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 789bcbed..b90df28e 100644 --- a/web/app/static/js/modules/corpus_analysis/client/Client.js +++ b/web/app/static/js/modules/corpus_analysis/client/Client.js @@ -17,7 +17,8 @@ class Client { this.requestQueryProgress = 0; this.socket = socket; this.socketEventListeners = {}; - this.recivedMetaData = false; + this.connected = false; + /** * Disable all console logging. @@ -61,10 +62,16 @@ class Client { /** * This functions sends events to the View to trigger specific functions that - * are handleing the represnetation of data stored in the model. + * are handleing the representation of data stored in the model. */ - notifyView(SendWhatToDo) { - + notifyView(caseIdentifier, msg=null) { + const event = new CustomEvent('notify', { detail: { + 'caseIdentifier': caseIdentifier, + 'msg': msg + } + }); + console.info('Dispatching Notification:', caseIdentifier); + document.dispatchEvent(event); } // Registers a CorpusAnalysisDisplay object to the Client. @@ -72,31 +79,6 @@ class Client { this.displays[type] = corpusAnalysisDisplay; } - /** - * Function that takes one or more query selector - * strings in an array as an input. The function then creates a - * class field in the client 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 - * method. - */ - // TODO: multipleResults=false, atattchSomeCallback=false ? - getHTMLElements(arrayOfSelectors) { - for (let selector of arrayOfSelectors) { - let element = document.querySelector(selector); - let cleanKey = []; - selector = selector.replace('_', '-'); - 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; - } - } - /** * Connects to the corpus analysis session for the specified corpus via * socket.io. @@ -159,8 +141,7 @@ class SocketEventListener { */ executeCallbacks(payload) { for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) { - listenerCallback.args.unshift(payload); - listenerCallback.callbackFunction(...listenerCallback.args); + listenerCallback.callbackFunction(payload, ...listenerCallback.args); } } } 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 ed28c2cf..ed0f182a 100644 --- a/web/app/static/js/modules/corpus_analysis/client/callbacks.js +++ b/web/app/static/js/modules/corpus_analysis/client/callbacks.js @@ -6,7 +6,7 @@ function saveMetaData() { let [payload, client, results, rest] = arguments; results.metaData.init(payload) client.recivedMetaData = true; - console.info('Metada saved:', results.metaData); + console.info('Metada saved:', results); } /** @@ -20,14 +20,23 @@ function prepareQueryData() { let [payload, client, results, rest] = arguments; results.init(); client.requestQueryProgress = 0; - + client.notifyView('query-data-prepareing'); } function saveQueryData(args) { let [payload, client, results, rest] = arguments; - results.data.addData(payload); + // 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; client.requestQueryProgress = payload.progress; + client.notifyView('query-data-recieving') console.info('Query data chunk saved', results.data); + if (client.requestQueryProgress === 100) { + client.notifyView('query-data-recieved'); + } } function querySetup(payload, client) { 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 87262ffa..ea4fc41f 100644 --- a/web/app/static/js/modules/corpus_analysis/client/listeners.js +++ b/web/app/static/js/modules/corpus_analysis/client/listeners.js @@ -1,3 +1,9 @@ +/** + * 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. + */ + /** * Recieves a corpus analysis connected signal via socket.io. */ @@ -14,6 +20,7 @@ function recieveConnected(type, client) { console.info(`corpus_analysis_init: ${response.code} - ${response.msg}`); console.info('corpus_analysis_init: Initialization succeeded'); console.info(response); + client.notifyView('connected'); console.groupEnd(); // get meta data immediately client.getMetaData(); @@ -21,6 +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); console.groupEnd(); } }); 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 d7ffe819..3e8edf7e 100644 --- a/web/app/static/js/modules/corpus_analysis/model/Results.js +++ b/web/app/static/js/modules/corpus_analysis/model/Results.js @@ -8,7 +8,6 @@ class Results { constructor() { this.data = new Data(); this.metaData = new MetaData(); - this.resultsData = new Data(); this.subResultsData = new Data(); console.info('Initialized the Results object.'); } @@ -16,7 +15,6 @@ class Results { init() { this.data.init(); this.metaData.init(); - this.resultsData.init() this.subResultsData.init(); } @@ -32,6 +30,7 @@ class Data { this.text_lookup = {}; // same as above for all text ids this.match_count = matchCount; this.corpus_type = 'results'; + this.cpos_ranges = null; this.query = ''; } diff --git a/web/app/static/js/modules/corpus_analysis/view/Display.js b/web/app/static/js/modules/corpus_analysis/view/Display.js deleted file mode 100644 index e82dbb17..00000000 --- a/web/app/static/js/modules/corpus_analysis/view/Display.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * This class is used to create an Display object. - * Input is one HTMLElement that can then be hidden or shown depending on - * its CSS classes. - */ -class Display { - constructor(element) { - // with this function initalized modals can also be handeld - this.element = (() => {if (element instanceof HTMLElement) { - return element; - } else { - element = element['$el'][0]; - return element; - } - })(); - this.errorContainer = element.querySelector('.error-container'); - this.showOnError = element.querySelectorAll('.show-on-error'); - this.showOnSuccess = element.querySelectorAll('.show-on-success'); - this.showWhileWaiting = element.querySelectorAll('.show-while-waiting'); - this.hideOnComplete = element.querySelectorAll('.hide-on-complete') - } - - // Changes the visibility of its own - setVisibilityByStatus(status) { - switch (status) { - case 'error': - for (let element of this.showOnError) { - element.classList.remove('hide'); - } - for (let element of this.showOnSuccess) { - element.classList.add('hide'); - } - for (let element of this.showWhileWaiting) { - element.classList.add('hide'); - } - break; - case 'success': - for (let element of this.showOnError) { - element.classList.add('hide'); - - } - for (let element of this.showOnSuccess) { - element.classList.remove('hide'); - } - for (let element of this.showWhileWaiting) { - element.classList.add('hide'); - } - break; - case 'waiting': - for (let element of this.showOnError) { - element.classList.add('hide'); - } - for (let element of this.showOnSuccess) { - element.classList.add('hide'); - } - for (let element of this.showWhileWaiting) { - element.classList.remove('hide'); - } - break; - default: - // Hide all - for (let element of this.showOnError) { - element.classList.add('hide'); - } - for (let element of this.showOnSuccess) { - element.classList.add('hide'); - } - for (let element of this.showWhileWaiting) { - element.classList.add('hide'); - } - } - } -} \ 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 fa0e8b36..845aa114 100644 --- a/web/app/static/js/modules/corpus_analysis/view/ResultsView.js +++ b/web/app/static/js/modules/corpus_analysis/view/ResultsView.js @@ -1,10 +1,20 @@ +/** + * This class implements a NotificationListener that is listening for the + * specified + */ +class NotificationListener { + constructor(type, listenerFunction) { + this.listenerFunction = listenerFunction; + this.type = type; + } +} + /** * This class is implements a View which handles the reprensentation of the * data that has been fetched by the Client of the corpus_analysis. This view * only handles how the data is shown to the user. View extends the list.js * List class. */ - class ResultsList extends List { /** * If no options are given when a new instance of this class is created @@ -42,8 +52,58 @@ class ResultsList extends List { // 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. 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?) + this.notificationListeners = {}; } + /** + * Function that takes one or more query selector + * strings in an array as an input. The function then creates a + * 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 + * method. + */ + // TODO: multipleResults=false, atattchSomeCallback=false ? + getHTMLElements(arrayOfSelectors) { + for (let selector of arrayOfSelectors) { + let element = document.querySelector(selector); + let cleanKey = []; + selector = selector.replace('_', '-'); + 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; + } + } + + /** + * Register notificationListeners to the ResultsList. Which will listen for + * the specified event. + */ + setNotificationListeners(notificationListeners) { + for (let notificationListener of notificationListeners) { + this.notificationListeners[notificationListener.type] = notificationListener; + } + } + + /** + * Loads the notificationListeners so that hey will be listening to their + * assigned custom events. + */ + loadNotificationListeners() { + for (let [type, listener] of Object.entries(this.notificationListeners)) { + listener.listenerFunction(type, this); + } + } + + /** + * Creates cpos either from ranges or not. + */ helperCreateCpos(cpos_ranges, cpos_values) { let lc; let c; @@ -788,3 +848,6 @@ class ResultsList extends List { } } }; + +// export classses +export { NotificationListener, 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 new file mode 100644 index 00000000..daf55db0 --- /dev/null +++ b/web/app/static/js/modules/corpus_analysis/view/callbacks.js @@ -0,0 +1,35 @@ +/** + * This file contains all the callbacks triggered by the notificationListener. + */ + +function connectingCallback(resultsList, msg=null) { + resultsList.getHTMLElements(['#analysis-init-modal']); + resultsList.analysisInitModal = M.Modal.init(resultsList.analysisInitModal, + {dismissible: false}); + resultsList.analysisInitModal.open(); +} + +function connectedCallback(resultsList, msg=null) { + resultsList.analysisInitModal.close(); +} + +function connectingFaildeCallback(resultsList, msg=null) { + resultsList.getHTMLElements([ + '#analysis-init-progress', + '#analysis-init-error' + ]); + resultsList.analysisInitProgress.classList.toggle('hide'); + resultsList.analysisInitError.classList.toggle('hide'); + resultsList.analysisInitError.textContent = msg; +} + +function queryDataPreparingCallback(resultsList, msg=null) { + resultsList.getHTMLElements(['#interactions-menu']); + resultsList.interactionsMenu.classList.toggle('hide', false) +} + +// export the callbacks +export { connectingCallback, + connectedCallback, + connectingFaildeCallback, + queryDataPreparingCallback, }; \ 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 new file mode 100644 index 00000000..34201cdd --- /dev/null +++ b/web/app/static/js/modules/corpus_analysis/view/listeners.js @@ -0,0 +1,57 @@ +/** + * 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 + * callback functions depending on the detail information of the notification + * event. + */ + +import { + connectingCallback, + connectedCallback, + connectingFaildeCallback, + queryDataPreparingCallback, +} from './callbacks.js'; + +function recieveNotification(eventType, resultsList) { + document.addEventListener(eventType, (event) => { + let caseIdentifier = event.detail.caseIdentifier; + switch (caseIdentifier) { + case 'connecting': + console.info('Recieved notification:', caseIdentifier); + connectingCallback(resultsList); + // execute callback + break; + case 'connected': + console.info('Recieved notification:', caseIdentifier); + connectedCallback(resultsList); + break; + case 'connecting-failed': + console.info('Recieved notification:', caseIdentifier); + // execute callback + connectingFaildeCallback(resultsList, event.detail.msg); + break; + case 'query-data-prepareing': + console.info('Recieved notification:', caseIdentifier); + // execute callback + queryDataPreparingCallback(resultsList); + break; + case 'query-data-recieving': + console.info('Recieved notification:', caseIdentifier); + // execute callback + break; + case 'query-data-recieved': + console.info('Recieved notification:', caseIdentifier); + // execute callback + break; + default: + console.error('Recieved unkown notification case identifier'); + // do something to not crash the analysis session? + // maybe unnecessary + } + }); +} + +// export listeners +export { recieveNotification }; \ No newline at end of file diff --git a/web/app/templates/corpora/analyse_corpus.html.j2 b/web/app/templates/corpora/analyse_corpus.html.j2 index 287b6be3..e7f5d4ac 100644 --- a/web/app/templates/corpora/analyse_corpus.html.j2 +++ b/web/app/templates/corpora/analyse_corpus.html.j2 @@ -41,8 +41,7 @@
-
-
+
{% include 'interactions/infos.html.j2' %} {% include 'interactions/export.html.j2' %} {% include 'interactions/create.html.j2' %} @@ -90,6 +89,13 @@ import { saveQueryData, saveMetaData, } from '../../static/js/modules/corpus_analysis/client/callbacks.js'; +import { + NotificationListener, + ResultsList, +} from '../../static/js/modules/corpus_analysis/view/ResultsView.js'; +import { + recieveNotification, +} from '../../static/js/modules/corpus_analysis/view/listeners.js'; /** * Second Phase: @@ -104,9 +110,11 @@ document.addEventListener("DOMContentLoaded", () => { 'dynamicMode': true}); /** * Initializing the results object as a model holding all the data of a query. - * Also holds the metadata of one query. + * Also holds the metadata of one query. After that initialize the ResultsList + * object as the View handeling the represnetation of the data. */ let results = new Results(); + let resultsView = new ResultsList('result-list', ResultsList.options); /** * Register listeners listening to socket.io events and their callbacks * Afterwards load them. @@ -136,8 +144,17 @@ document.addEventListener("DOMContentLoaded", () => { listenForQueryData, listenForMetaData]); client.loadSocketEventListeners(); + /** + * Register resultsView listeners listening to nitification events. + */ + const listenForNotification = new NotificationListener('notify', + recieveNotification); + resultsView.setNotificationListeners([listenForNotification]); + resultsView.loadNotificationListeners(); // Connect client to server + client.notifyView('connecting'); client.connect(); + // Send a query and recieve its answer data let queryFormElement = document.getElementById('query-form'); queryFormElement.addEventListener('submit', (event) => { diff --git a/web/app/templates/modals/analysis_init.html.j2 b/web/app/templates/modals/analysis_init.html.j2 index dfe2b948..b7e54026 100644 --- a/web/app/templates/modals/analysis_init.html.j2 +++ b/web/app/templates/modals/analysis_init.html.j2 @@ -1,12 +1,17 @@ -