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 09566c29..122add1e 100644 --- a/web/app/static/js/modules/corpus_analysis/client/Client.js +++ b/web/app/static/js/modules/corpus_analysis/client/Client.js @@ -16,7 +16,7 @@ class Client { this.logging = logging; this.requestQueryProgress = 0; this.socket = socket; - this.socketEventListeners = {}; + this.eventListeners = {}; this.connected = false; @@ -44,9 +44,9 @@ class Client { } // Registers one or more SocketEventListeners to the Client. - setSocketEventListeners(socketEventListeners) { - for (let socketEventListener of socketEventListeners) { - this.socketEventListeners[socketEventListener.type] = socketEventListener; + setSocketEventListeners(eventListeners) { + for (let eventListener of eventListeners) { + this.eventListeners[eventListener.type] = eventListener; } } @@ -55,7 +55,7 @@ class Client { * type strings because they double as the socket event event names. */ loadSocketEventListeners() { - for (let [type, listener] of Object.entries(this.socketEventListeners)) { + for (let [type, listener] of Object.entries(this.eventListeners)) { listener.listenerFunction(type, this); } } @@ -66,8 +66,8 @@ class Client { */ notifyView(caseIdentifier, detailObject={}) { detailObject.caseIdentifier = caseIdentifier; - const event = new CustomEvent('notify', { detail: detailObject }); - console.info('Dispatching Notification:', caseIdentifier); + const event = new CustomEvent('notify-view', { detail: detailObject }); + console.info('Client dispatching Notification:', caseIdentifier); document.dispatchEvent(event); } @@ -105,16 +105,38 @@ class Client { 'socket.emit for the query', queryStr); this.socket.emit('corpus_analysis_query', queryStr); } + + // create results data either from all results or from all marked sub results + getResultsData(resultsType, dataIndexes, results) { + // TODO: where to put all the stuff that deactivates all the buttons because cqp server cannot handle mutliple requests? + // Triggers emit to get full match context from server for a number of + // matches identified by their data_index. + let tmp_first_cpos = []; + let tmp_last_cpos = []; + for (let dataIndex of dataIndexes) { + tmp_first_cpos.push(results.data.matches[dataIndex].c[0]); + tmp_last_cpos.push(results.data.matches[dataIndex].c[1]); + } + nopaque.socket.emit("corpus_analysis_inspect_match", + { + type: resultsType, + data_indexes: dataIndexes, + first_cpos: tmp_first_cpos, + last_cpos: tmp_last_cpos, + }); + } + } + /** * This class is used to create an SocketEventListener. * Input are an identifying type string, the listener function and callbacks * which will be executed as part of the listener function. The identifying * type string is also used as the socket event event identifier. */ -class SocketEventListener { - constructor(type, listenerFunction, args=null) { +class ClientEventListener { + constructor(type, listenerFunction) { this.listenerCallbacks = {}; this.listenerFunction = listenerFunction; this.type = type; @@ -127,8 +149,8 @@ class SocketEventListener { } } - /** Shorthand to execute all registered callbacks with same args in insertion - * order. + /** Shorthand to execute all registered callbacks with same defaultArgs + * in insertion order. * NOTE: * Since ECMAScript 2015, objects do preserve creation order for * string and Symbol keys. In JavaScript engines that comply with the @@ -136,11 +158,18 @@ class SocketEventListener { * yield the keys in order of insertion. * So all modern Browsers. */ - executeCallbacks(payload) { + executeCallbacks(defaultArgs) { for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) { - listenerCallback.callbackFunction(payload, ...listenerCallback.args); + listenerCallback.callbackFunction(...defaultArgs, + ...listenerCallback.args); } } + // use this if you only want to execute a specific registered callback + executeCallback(defaultArgs, type) { + let listenerCallback = this.listenerCallbacks[type]; + listenerCallback.callbackFunction(...defaultArgs, + ...listenerCallback.args); + } } /** @@ -159,6 +188,6 @@ class ListenerCallback { // export Classes from this module export { Client, - SocketEventListener, + ClientEventListener, ListenerCallback, }; \ No newline at end of file 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 afda9fe7..145bdaa5 100644 --- a/web/app/static/js/modules/corpus_analysis/client/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/client/callbacks.js @@ -46,116 +46,24 @@ function saveQueryData(args) { } } -function querySetup(payload, client) { - // deletes old data from query issued before this new query - client.results.clearAll(); - // load necessary HTMLElements with selectory syntax and save them as fields - client.getHTMLElements(['#query-progress-bar', '#query-results-user-feedback', - '#recieved-match-count', '#total-match-count', - '#text-lookup-count', '#text-lookup-titles', - '#query-results-create', '#add-to-sub-results']); - client.recievedMatchCount.textContent = 0; - client.totalMatchCount.textContent = `${payload.match_count}`; - client.queryResultsUserFeedback.classList.toggle('hide'); - client.queryProgressBar.classList.toggle('hide'); - client.queryProgressBar.lastElementChild.style.width = '0%'; - if (client.dynamicMode) { - client.addToSubResults.toggleAttribute('disabled'); - client.queryResultsCreate.classList.toggle('disabled'); - } +function getResultsData(args) { + let [resultsType, dataIndexes, client, results, rest] = arguments; + client.notifyView('results-data-recieving'); + client.getResultsData(resultsType, dataIndexes, results); } -/** - * This callback should be registered to the SocketEventListener 'recieveQueryData' - * It takes the incoming chunk and renders the results using the - * results.jsList object. It can either handle live incoming data chunks or - * already loaded/imported results data. - */ -function queryRenderResults(payload, client) { - client.getHTMLElements(['#recieved-match-count', '#match-count', - '#display-options-form-expert_mode']); - const renderResults = (data) => { - /** - * resultItem saves the incoming chunk matches as objects to add those later - * to the client.results.jsList - */ - let resultItems = []; - // get infos for full match row - for (let [index, match] of data.matches.entries()) { - resultItems.push({...match, ...{'index': index + client.results.data.matches.length}}); - } - client.results.jsList.add(resultItems, (items) => { - for (let item of items) { - item.elm = client.results.jsList.createResultRowElement(item, data); - } - }); - } - if (client.dynamicMode) { - if (payload.chunk.cpos_ranges == true) { - client.results.data['cpos_ranges'] = true; - } else { - client.results.data['cpos_ranges'] = false; - } - renderResults(payload.chunk); - helperQueryRenderResults(payload, client); - console.info('Result progress is:', client.requestQueryProgress); - if (client.requestQueryProgress === 100) { - /** - * activate, hide or show elements if all reults have been recieved - * also load some new elements taht have not ben loaded before - */ - client.queryProgressBar.classList.toggle('hide'); - client.queryResultsUserFeedback.classList.toggle('hide'); - client.queryResultsCreate.classList.toggle('disabled'); - client.addToSubResults.toggleAttribute('disabled'); - // addToSubResultsElement.removeAttribute("disabled"); - // // inital expert mode check and sub results activation - // client.results.jsList.activateInspect(); - // if (addToSubResultsElement.checked) { - // client.results.jsList.activateAddToSubResults(); - // } - // if (expertModeSwitchElement.checked) { - // client.results.jsList.expertModeOn("query-display"); - // } - } - } else if (!client.dynamicMode) { - renderResults(payload); - helperQueryRenderResults({'chunk': payload}, client); - client.queryProgressBar.classList.toggle('hide'); - client.queryResultsUserFeedback.classList.toggle('hide'); - client.results.jsList.activateInspect(); - if (client.displayOptionsFormExpertMode.checked) { - client.results.jsList.expertModeOn("query-display"); - } - } -} - -/** - * Helper function that saves result data into the client.results.data object. - * Also does some showing and hiding of Elements and user feedback text. - */ -function helperQueryRenderResults (payload, client) { - // updating table on finished item creation callback via createResultRowElement - client.results.jsList.update(); - client.results.jsList.changeContext(); // sets lr context on first result load - // incorporating new chunk results into full results - client.results.data.matches.push(...payload.chunk.matches); - client.results.data.addData(payload.chunk.cpos_lookup, 'cpos_lookup'); - client.results.data.addData(payload.chunk.text_lookup, 'text_lookup'); - // complete metaData - // client.results.metaData.add(); - // show user current and total match count - client.recievedMatchCount.textContent = `${client.results.data.matches.length}`; - client.textLookupCount.textContent = `${Object.keys(client.results.data.text_lookup).length}`; - let titles = new Array(); - for (let [key, value] of Object.entries(client.results.data.text_lookup)) { - titles.push(`${value.title} (${value.publishing_year})`); - }; - client.textLookupTitles.textContent = `${titles.join(", ")}`; - // update progress bar and requestQueryProgress - client.queryProgressBar.lastElementChild.style.width = `${payload.progress}%`; - client.requestQueryProgress = payload.progress; +function saveResultsData(args) { + let [payload, client, results, rest] = arguments; + // code to save results data depending on type + console.info('Results data has been saved.'); + client.notifyView('results-data-recieved'); } // export callbacks -export { prepareQueryData, saveMetaData, saveQueryData }; \ No newline at end of file +export { + prepareQueryData, + saveMetaData, + saveQueryData, + getResultsData, + saveResultsData, +}; \ No newline at end of file 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 681bf6c6..11bfc22b 100644 --- a/web/app/static/js/modules/corpus_analysis/client/listeners.js +++ b/web/app/static/js/modules/corpus_analysis/client/listeners.js @@ -1,9 +1,14 @@ /** * This file contains the listener functions which can be assigned to the * coprus_analysis client. So that the incoming data/status informations will - * be handled. + * be handled. There are several listeners listening for socket .io events. + * Further below one javascript custom event listener is specified. This + * listener listens for javascript custom events which are being dispatched by + * the View (resultsList). */ +// Listeners for socket io events + /** * Recieves a corpus analysis connected signal via socket.io. */ @@ -50,7 +55,7 @@ function recieveMetaData(type, client) { console.info(`corpus_analysis_meta_data: ${response.code} - ${response.msg}`); console.info(response); // executing the registered callbacks - client.socketEventListeners[type].executeCallbacks(response.payload); + client.eventListeners[type].executeCallbacks([response.payload]); console.groupEnd(); } else { console.group('Failed to recieve meta data.'); @@ -81,7 +86,7 @@ function recieveQueryStatus(type, client) { console.info(`corpus_analysis_query: ${response.code} - ${response.msg}`); console.info(response); // executing the registered callbacks - client.socketEventListeners[type].executeCallbacks(response.payload); + client.eventListeners[type].executeCallbacks([response.payload]); console.groupEnd(); } else { console.group('corpus_analysis_query: Client failed recieving', @@ -113,7 +118,7 @@ function recieveQueryData(type, client) { /** * Execute registered callbacks and notify View. */ - client.socketEventListeners[type].executeCallbacks(response.payload); + client.eventListeners[type].executeCallbacks([response.payload]); console.info('Added chunk data to results.data.'); console.groupEnd(); } else { @@ -125,18 +130,72 @@ function recieveQueryData(type, client) { } }); } else { - console.group('corpus_analysis_query_results: Loading query data.') + console.group('corpus_analysis_query_results: Loading query data.'); console.info('Client loading imported query data from database.'); // executing the registered callbacks - client.socketEventListeners[type].executeCallbacks(); + client.eventListeners[type].executeCallbacks(); console.groupEnd(); } } +/** + * Recieves the data requested by the create Results or sub results button + */ +function recieveResultsData(type, client) { + client.socket.on(type, (response) => { + /** + * Check if request for session was OK. + * If OK execute registered callbacks and notify View. + */ + if (response.code === 200) { + console.group('Client recieving results data') + console.info('corpus_analysis_inspect_match: Client recieving results data', + 'via socket.on'); + console.info(`corpus_analysis_inspect_match: ${response.code} - ${response.msg}`); + console.info(response); + // executing the registered callbacks + client.eventListeners[type].executeCallbacks([response.payload]); + console.groupEnd(); + } else { + console.group('Failed to recieve results data.'); + console.error('corpus_analysis_inspect_match: Client failed to recieve', + 'results data via socket.on'); + let errorText = `Error ${response.payload.code} - ${response.payload.msg}`; + console.error(`corpus_analysis_inspect_match: ${errorText}`); + console.groupEnd(); + } + }); +} + +/* + * This is the javascript custom event listener, listening for events + * dispatched by the View. + */ +function recieveViewNotification(type, client) { + document.addEventListener(type, (event) => { + let caseIdentifier = event.detail.caseIdentifier; + switch(caseIdentifier) { + case 'get-results': + console.info('Client getting full results for export.'); + // execute callback or functions + client.eventListeners[type].executeCallback([event.detail.resultsType, + event.detail.dataIndexes], + caseIdentifier); + break + default: + console.error('Recieved unkown notification case identifier from View'); + // do something to not crash the analysis session? + // maybe unnecessary + } + }); +} + // export listeners from this module export { recieveConnected, recieveMetaData, recieveQueryStatus, - recieveQueryData + recieveQueryData, + recieveViewNotification, + recieveResultsData, }; \ No newline at end of file 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 3e8edf7e..c2f0c3bc 100644 --- a/web/app/static/js/modules/corpus_analysis/model/Results.js +++ b/web/app/static/js/modules/corpus_analysis/model/Results.js @@ -8,6 +8,7 @@ class Results { constructor() { this.data = new Data(); this.metaData = new MetaData(); + this.fullResultsData = new Data(); this.subResultsData = new Data(); console.info('Initialized the Results object.'); } @@ -15,12 +16,12 @@ class Results { init() { this.data.init(); this.metaData.init(); + this.fullResultsData = new Data(); this.subResultsData.init(); } } - class Data { // Sets empty object structure. Also usefull to delete old results. // matchCount default is 0 @@ -94,29 +95,6 @@ class Data { this.download(downloadElement, dataStr, resultFilename, "text/json", ".json") } - // create results data either from all results or from al lmarked sub results - createResultsData(type) { - // deactivate inspect, because cqp server cannot handle multiple requests - results.jsList.deactivateInspect(); - activateInspectInteraction.setCallback("noCheck", - results.jsList.deactivateInspect, - results.jsList); - // set flag that results are being created to avoid reactivation of - // sub results creation if marked matches are changed - resultCreationRunning = true; - console.log(resultCreationRunning); - if (type === "sub-results") { - resultsCreateElement.classList.add("disabled"); // cqp server cannot handle more than one request at a time. Thus we deactivate the resultsCreateElement - let tmp = [...results.jsList.addToSubResultsIdsToShow].sort(function(a, b){return a-b}); - let dataIndexes = []; - tmp.forEach((index) => dataIndexes.push(index - 1)); - results.jsList.getMatchWithContext(dataIndexes, "sub-results"); - } else if (type === "results") { - subResultsCreateElement.classList.add("disabled"); // cqp server cannot handle more than one request at a time. Thus we deactivate the subResultsCreateElement - let dataIndexes = [...Array(results.data.match_count).keys()]; - results.jsList.getMatchWithContext(dataIndexes, "results"); - } - } } class MetaData { 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 146913ed..6eb5e5c5 100644 --- a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js +++ b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js @@ -2,7 +2,7 @@ * This class implements a NotificationListener that is listening for the * specified */ -class NotificationListener { +class ViewEventListener { constructor(type, listenerFunction) { this.listenerFunction = listenerFunction; this.type = type; @@ -61,7 +61,7 @@ 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 fetched with the querySelector + * The value will be the identifed element or elements fetched with the querySelector * method. */ // TODO: multipleResults=false, atattchSomeCallback=false ? @@ -73,6 +73,7 @@ class ResultsList extends List { element = document.querySelector(selector); } else { elements = document.querySelectorAll(selector); + elements = [...elements]; } let cleanKey = []; selector = selector.replace(/_/g, '-'); @@ -106,6 +107,17 @@ class ResultsList extends List { } } + /** + * This functions sends events to the Client to trigger specific functions to + * trigger new data requests from the server. + */ + notifyClient(caseIdentifier, detailObject={}) { + detailObject.caseIdentifier = caseIdentifier; + const event = new CustomEvent('notify-client', { detail: detailObject }); + console.info('Client dispatching Notification:', caseIdentifier); + document.dispatchEvent(event); + } + /** * Creates cpos either from ranges or not. */ @@ -255,25 +267,6 @@ class ResultsList extends List { } } - // Triggers emit to get full match context from server for a number of - // matches identified by their data_index. - getMatchWithContext(dataIndexes, type) { - let tmp_first_cpos = []; - let tmp_last_cpos = []; - for (let dataIndex of dataIndexes) { - tmp_first_cpos.push(results.data.matches[dataIndex].c[0]); - tmp_last_cpos.push(results.data.matches[dataIndex].c[1]); - } - nopaque.socket.emit("corpus_analysis_inspect_match", - { - type: type, - data_indexes: dataIndexes, - first_cpos: tmp_first_cpos, - last_cpos: tmp_last_cpos, - } - ); - } - // ###### Functions to inspect one match, to show more details ###### // activate inspect buttons if progress is 100 activateInspect() { @@ -786,7 +779,7 @@ class ResultsList extends List { return matchRowElement } - // creates the HTML table code for the metadata vie in the corpus analysis interface + // creates the HTML table code for the metadata view in the corpus analysis interface createMetaDataForModal(metaDataObject) { let html = `