Work on View notifications

This commit is contained in:
Stephan Porada 2020-08-24 16:33:37 +02:00
parent 0507ae4e34
commit d2453c2cc3
11 changed files with 218 additions and 117 deletions

View File

@ -17,7 +17,8 @@ class Client {
this.requestQueryProgress = 0; this.requestQueryProgress = 0;
this.socket = socket; this.socket = socket;
this.socketEventListeners = {}; this.socketEventListeners = {};
this.recivedMetaData = false; this.connected = false;
/** /**
* Disable all console logging. * Disable all console logging.
@ -61,10 +62,16 @@ class Client {
/** /**
* This functions sends events to the View to trigger specific functions that * 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. // Registers a CorpusAnalysisDisplay object to the Client.
@ -72,31 +79,6 @@ class Client {
this.displays[type] = corpusAnalysisDisplay; 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 * Connects to the corpus analysis session for the specified corpus via
* socket.io. * socket.io.
@ -159,8 +141,7 @@ class SocketEventListener {
*/ */
executeCallbacks(payload) { executeCallbacks(payload) {
for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) { for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) {
listenerCallback.args.unshift(payload); listenerCallback.callbackFunction(payload, ...listenerCallback.args);
listenerCallback.callbackFunction(...listenerCallback.args);
} }
} }
} }

View File

@ -6,7 +6,7 @@ function saveMetaData() {
let [payload, client, results, rest] = arguments; let [payload, client, results, rest] = arguments;
results.metaData.init(payload) results.metaData.init(payload)
client.recivedMetaData = true; 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; let [payload, client, results, rest] = arguments;
results.init(); results.init();
client.requestQueryProgress = 0; client.requestQueryProgress = 0;
client.notifyView('query-data-prepareing');
} }
function saveQueryData(args) { function saveQueryData(args) {
let [payload, client, results, rest] = arguments; 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.requestQueryProgress = payload.progress;
client.notifyView('query-data-recieving')
console.info('Query data chunk saved', results.data); console.info('Query data chunk saved', results.data);
if (client.requestQueryProgress === 100) {
client.notifyView('query-data-recieved');
}
} }
function querySetup(payload, client) { function querySetup(payload, client) {

View File

@ -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. * 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: ${response.code} - ${response.msg}`);
console.info('corpus_analysis_init: Initialization succeeded'); console.info('corpus_analysis_init: Initialization succeeded');
console.info(response); console.info(response);
client.notifyView('connected');
console.groupEnd(); console.groupEnd();
// get meta data immediately // get meta data immediately
client.getMetaData(); client.getMetaData();
@ -21,6 +28,7 @@ function recieveConnected(type, client) {
let errorText = `Error ${response.code} - ${response.msg}`; let errorText = `Error ${response.code} - ${response.msg}`;
console.group('Connection failed!') console.group('Connection failed!')
console.error(`corpus_analysis_init: ${errorText}`); console.error(`corpus_analysis_init: ${errorText}`);
client.notifyView('conntecting-failed', errorText);
console.groupEnd(); console.groupEnd();
} }
}); });

View File

@ -8,7 +8,6 @@ class Results {
constructor() { constructor() {
this.data = new Data(); this.data = new Data();
this.metaData = new MetaData(); this.metaData = new MetaData();
this.resultsData = new Data();
this.subResultsData = new Data(); this.subResultsData = new Data();
console.info('Initialized the Results object.'); console.info('Initialized the Results object.');
} }
@ -16,7 +15,6 @@ class Results {
init() { init() {
this.data.init(); this.data.init();
this.metaData.init(); this.metaData.init();
this.resultsData.init()
this.subResultsData.init(); this.subResultsData.init();
} }
@ -32,6 +30,7 @@ class Data {
this.text_lookup = {}; // same as above for all text ids this.text_lookup = {}; // same as above for all text ids
this.match_count = matchCount; this.match_count = matchCount;
this.corpus_type = 'results'; this.corpus_type = 'results';
this.cpos_ranges = null;
this.query = ''; this.query = '';
} }

View File

@ -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');
}
}
}
}

View File

@ -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 * 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 * 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 * only handles how the data is shown to the user. View extends the list.js
* List class. * List class.
*/ */
class ResultsList extends List { class ResultsList extends List {
/** /**
* If no options are given when a new instance of this class is created * 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. // 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.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. 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) { helperCreateCpos(cpos_ranges, cpos_values) {
let lc; let lc;
let c; let c;
@ -788,3 +848,6 @@ class ResultsList extends List {
} }
} }
}; };
// export classses
export { NotificationListener, ResultsList };

View File

@ -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, };

View File

@ -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 };

View File

@ -41,8 +41,7 @@
<div class="col s12" id="query-display"> <div class="col s12" id="query-display">
<div class="card"> <div class="card">
<div class="card-content" id="result-list" style="overflow: hidden;"> <div class="card-content" id="result-list" style="overflow: hidden;">
<div class="error-container hide show-on-error"></div> <div id="interactions-menu" class="row hide">
<div class=" row hide show-on-success">
{% include 'interactions/infos.html.j2' %} {% include 'interactions/infos.html.j2' %}
{% include 'interactions/export.html.j2' %} {% include 'interactions/export.html.j2' %}
{% include 'interactions/create.html.j2' %} {% include 'interactions/create.html.j2' %}
@ -90,6 +89,13 @@ import {
saveQueryData, saveQueryData,
saveMetaData, saveMetaData,
} from '../../static/js/modules/corpus_analysis/client/callbacks.js'; } 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: * Second Phase:
@ -104,9 +110,11 @@ document.addEventListener("DOMContentLoaded", () => {
'dynamicMode': true}); 'dynamicMode': true});
/** /**
* Initializing the results object as a model holding all the data of a query. * 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 results = new Results();
let resultsView = new ResultsList('result-list', ResultsList.options);
/** /**
* Register listeners listening to socket.io events and their callbacks * Register listeners listening to socket.io events and their callbacks
* Afterwards load them. * Afterwards load them.
@ -136,8 +144,17 @@ document.addEventListener("DOMContentLoaded", () => {
listenForQueryData, listenForQueryData,
listenForMetaData]); listenForMetaData]);
client.loadSocketEventListeners(); client.loadSocketEventListeners();
/**
* Register resultsView listeners listening to nitification events.
*/
const listenForNotification = new NotificationListener('notify',
recieveNotification);
resultsView.setNotificationListeners([listenForNotification]);
resultsView.loadNotificationListeners();
// Connect client to server // Connect client to server
client.notifyView('connecting');
client.connect(); client.connect();
// Send a query and recieve its answer data // Send a query and recieve its answer data
let queryFormElement = document.getElementById('query-form'); let queryFormElement = document.getElementById('query-form');
queryFormElement.addEventListener('submit', (event) => { queryFormElement.addEventListener('submit', (event) => {

View File

@ -1,12 +1,17 @@
<!-- Analysis init modal. User feedback showing that the analysis session is <!-- Analysis init modal. User feedback showing that the analysis session is
loading. --> loading. -->
<div class="modal no-autoinit" id="init-display"> <div class="modal no-autoinit" id="analysis-init-modal">
<div class="modal-content"> <div class="modal-content">
<h4>Initializing your corpus analysis session...</h4> <h4>Initializing your corpus analysis session...</h4>
<div class="error-container hide show-on-error"></div> <p>If the loading takes to long or an error occured,
<div class="hide progress show-while-waiting"> <a onclick="window.location.reload()" href="#">click here</a>
to refresh your session or
<a href="{{ url_for('corpora.corpus', corpus_id=corpus_id) }}">go back</a>!
</p>
<div id="analysis-init-progress" class="progress">
<div class="indeterminate"></div> <div class="indeterminate"></div>
</div> </div>
<p id="analysis-init-error" class="hide red-text"></p>
</div> </div>
</div> </div>

View File

@ -34,7 +34,7 @@
<div class="col s12" id="query-display"> <div class="col s12" id="query-display">
<div class="card"> <div class="card">
<div class="card-content" id="result-list" style="overflow: hidden;"> <div class="card-content" id="result-list" style="overflow: hidden;">
<div class=" row show-on-success"> <div class="row">
{% include 'interactions/infos.html.j2' %} {% include 'interactions/infos.html.j2' %}
{% include 'interactions/display.html.j2' %} {% include 'interactions/display.html.j2' %}
{% include 'interactions/analysis.html.j2' %} {% include 'interactions/analysis.html.j2' %}