first change with new MVCish pattern

This commit is contained in:
Stephan Porada 2020-08-21 16:33:47 +02:00
parent dabeb818d1
commit 0507ae4e34
8 changed files with 308 additions and 154 deletions

View File

@ -30,7 +30,7 @@ def init_corpus_analysis(corpus_id):
corpus_id, current_user.id, request.sid)
@socketio.on('corpus_analysis_get_meta_data')
@socketio.on('corpus_analysis_meta_data')
@socketio_login_required
def corpus_analysis_get_meta_data(corpus_id):
# get meta data from db
@ -69,7 +69,7 @@ def corpus_analysis_get_meta_data(corpus_id):
payload = metadata
response = {'code': 200, 'desc': 'Corpus meta data', 'msg': 'OK',
'payload': payload}
socketio.emit('corpus_analysis_send_meta_data', response, room=request.sid)
socketio.emit('corpus_analysis_meta_data', response, room=request.sid)
@socketio.on('corpus_analysis_query')

View File

@ -1,10 +1,10 @@
/**
* This class is used to create a Client object.
* The client handels the client server communication.
* It requests data (e.g. the analysis session or query results) from the
* the server and recieves them, if it dynamicMode is true.
* It communicates with the server (e.g. connection or query)
* and recieves data from it, if dynamicMode is true.
* If dynamicMode is false, the client can also handle data that is already
* loaded and not coming in in chunks.
* loaded and is not coming in in chunks.
*/
class Client {
constructor({corpusId = null,
@ -12,16 +12,18 @@ class Client {
logging = true,
dynamicMode = true} = {}) {
this.corpusId = corpusId;
this.displays = {};
this.dynamicMode = dynamicMode;
this.logging = logging;
this.requestQueryProgress = 0;
this.results = undefined; // holds the results object later on
this.socket = socket;
this.socketEventListeners = {};
this.recivedMetaData = false;
/**
* Disable all console logging.
* This is global. So every other log message in every other Class or
* function used in conjunction with the client either logs or does not
* log depending on the logging flag.
* Credits to https://gist.github.com/kmonsoor/0244fdb4ad79a4826371e58a1a5fa984
*/
if (!logging) {
@ -37,6 +39,7 @@ class Client {
});
})();
}
console.info("Client initialized:", this);
}
// Registers one or more SocketEventListeners to the Client.
@ -56,10 +59,10 @@ class Client {
}
}
// TODO: get rid of this disply stuff and send commands to the viewer
// on what to do/show/hide etc
/**
* This functions sends events to the View to trigger specific functions that
* are handleing the represnetation of data stored in the model.
*/
notifyView(SendWhatToDo) {
}
@ -108,6 +111,12 @@ class Client {
this.socket.emit('corpus_analysis_init', this.corpusId);
}
getMetaData() {
console.info('corpus_analysis_meta_data: Client getting meta data via',
'socket.emit.');
this.socket.emit('corpus_analysis_meta_data', this.corpusId);
}
/**
* Emits query to the server via socket.io. Server will send the results
* back.
@ -119,80 +128,6 @@ class Client {
}
}
/**
* This class is used to create an CorpusAnalysisDisplay object.
* Input is one HTMLElement that can then be hidden or shown depending on
* its CSS classes.
*/
class CorpusAnalysisDisplay {
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');
}
}
}
}
/**
* This class is used to create an SocketEventListener.
* Input are an identifying type string, the listener function and callbacks
@ -200,7 +135,7 @@ class CorpusAnalysisDisplay {
* type string is also used as the socket event event identifier.
*/
class SocketEventListener {
constructor(type, listenerFunction) {
constructor(type, listenerFunction, args=null) {
this.listenerCallbacks = {};
this.listenerFunction = listenerFunction;
this.type = type;
@ -222,9 +157,10 @@ class SocketEventListener {
* yield the keys in order of insertion.
* So all modern Browsers.
*/
executeCallbacks(args) {
executeCallbacks(payload) {
for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) {
listenerCallback.callbackFunction(...args);
listenerCallback.args.unshift(payload);
listenerCallback.callbackFunction(...listenerCallback.args);
}
}
}
@ -235,7 +171,8 @@ class SocketEventListener {
* callback functions.
*/
class ListenerCallback {
constructor(type, callbackFunction) {
constructor(type, callbackFunction, argsList) {
this.args = argsList;
this.callbackFunction = callbackFunction;
this.type = type;
}
@ -245,6 +182,5 @@ class ListenerCallback {
export {
Client,
SocketEventListener,
CorpusAnalysisDisplay,
ListenerCallback,
};

View File

@ -1,9 +1,35 @@
/**
* This callback is called on a socket.on "corpus_analysis_send_meta_data".
* Handels incoming corpus metadata
*/
function saveMetaData() {
let [payload, client, results, rest] = arguments;
results.metaData.init(payload)
client.recivedMetaData = true;
console.info('Metada saved:', results.metaData);
}
/**
* This callback should be registered to the SocketEventListener 'recieveQueryStatus'.
* It just gets the incoming status data of the issued query
* and does some preperation work like hiding or showing elements and deleting
* the data from the last query.
*/
function prepareQueryData() {
// deletes old data from query issued before this new query
let [payload, client, results, rest] = arguments;
results.init();
client.requestQueryProgress = 0;
}
function saveQueryData(args) {
let [payload, client, results, rest] = arguments;
results.data.addData(payload);
client.requestQueryProgress = payload.progress;
console.info('Query data chunk saved', results.data);
}
function querySetup(payload, client) {
// deletes old data from query issued before this new query
client.results.clearAll();
@ -12,7 +38,6 @@ function querySetup(payload, client) {
'#recieved-match-count', '#total-match-count',
'#text-lookup-count', '#text-lookup-titles',
'#query-results-create', '#add-to-sub-results']);
client.requestQueryProgress = 0;
client.recievedMatchCount.textContent = 0;
client.totalMatchCount.textContent = `${payload.match_count}`;
client.queryResultsUserFeedback.classList.toggle('hide');
@ -117,4 +142,4 @@ function helperQueryRenderResults (payload, client) {
}
// export callbacks
export { querySetup, queryRenderResults };
export { prepareQueryData, saveMetaData, saveQueryData };

View File

@ -1,40 +1,57 @@
import { querySetup, queryRenderResults } from './nopaque.listenerCallbacks.js'
/**
* Recieves a corpus analysis session via socket.io.
* Closes the loading modal that has been opend with requestSession at the
* start of the request.
* Recieves a corpus analysis connected signal via socket.io.
*/
function recieveSession(type, client) {
function recieveConnected(type, client) {
client.socket.on(type, (response) => {
/**
* Check if request for session was OK.
* If OK execute callbacks and hide/show displays.
* If OK execute registered callbacks and notify View.
*/
console.group('recieve session')
if (response.code === 200) {
console.info('corpus_analysis_init: Client recieving session/or error',
console.group('Connected!')
console.info('corpus_analysis_init: Client recieving connected codes',
'codes via socket.on');
console.info(`corpus_analysis_init: ${response.code} - ${response.msg}`);
console.info('corpus_analysis_init: Initialization succeeded');
console.info(response);
// Handling hide/show of displays
if (client.displays.init != undefined) {
client.displays.init.element.M_Modal.close();
client.displays.init.setVisibilityByStatus('success');
}
console.groupEnd();
// get meta data immediately
client.getMetaData();
} else {
let errorText = `Error ${response.code} - ${response.msg}`;
if (client.displays.init.errorContainer != undefined) {
client.displays.init.errorContainer.innerHTML = `<p class="red-text">`
+ `<i class="material-icons tiny">error</i>${errorText}</p>`;
}
if (client.displays.init != undefined) {
client.displays.init.setVisibilityByStatus('error');
}
console.group('Connection failed!')
console.error(`corpus_analysis_init: ${errorText}`);
console.groupEnd();
}
});
}
/**
* Recieves meta data from the server via socket.io.
*/
function recieveMetaData(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 meta data')
console.info('corpus_analysis_meta_data: Client recieving meta data',
'via socket.on');
console.info(`corpus_analysis_meta_data: ${response.code} - ${response.msg}`);
console.info(response);
// executing the registered callbacks
client.socketEventListeners[type].executeCallbacks(response.payload);
console.groupEnd();
} else {
console.group('Failed to recieve meta data.');
console.error('corpus_analysis_meta_data: Client failed to recieve',
'meta data via socket.on');
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
console.error(`corpus_analysis_meta_data: ${errorText}`);
console.groupEnd();
}
console.groupEnd();
});
}
@ -47,36 +64,27 @@ function recieveSession(type, client) {
function recieveQueryStatus(type, client) {
client.socket.on(type, (response) => {
/**
* Check if issued query was OK.
* If OK execute registered callbacks and hide/show displays.
* Check if request for session was OK.
* If OK execute registered callbacks and notify View.
*/
console.group('corpus_analysis_query: Client recieving query process',
'status via socket.on');
if (response.code === 200) {
console.group('corpus_analysis_query: Client recieving query process',
'status via socket.on');
console.info(`corpus_analysis_query: ${response.code} - ${response.msg}`);
console.info(response);
// Handling hide/show of displays
if (client.displays.query != undefined) {
client.displays.query.setVisibilityByStatus("success");
}
// executing the registered callbacks
client.socketEventListeners[type].executeCallbacks([response.payload, client]);
client.socketEventListeners[type].executeCallbacks(response.payload);
console.groupEnd();
} else {
console.group('corpus_analysis_query: Client failed recieving',
'query process status via socket.on');
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
if (response.payload.code == 1281) {
errorText += ' - Invalid Query';
}
nopaque.flash(errorText, "error");
if (client.displays.query.errorContainer != undefined) {
client.displays.query.errorContainer.innerHTML = `<p class="red-text">`+
`<i class="material-icons tiny">error</i> ${errorText}</p>`;
}
if (client.displays.query != undefined) {
client.displays.query.setVisibilityByStatus("error");
}
console.error(`corpus_analysis_query: ${errorText}`);
console.groupEnd();
}
console.groupEnd();
});
}
@ -84,24 +92,43 @@ function recieveQueryStatus(type, client) {
* Recieves the query data from the request and handles it.
*/
function recieveQueryData(type, client) {
console.group('corpus_analysis_query_results: Client recieving or loading',
'query data.');
/**
* Check if request for session was OK.
* If OK execute registered callbacks and notify View.
*/
if (client.dynamicMode) {
console.info('Client recieving query data via socket.on');
client.socket.on(type, (response) => {
console.info('Recieved chunk', response);
// executing the registered callbacks
client.socketEventListeners[type].executeCallbacks([response.payload, client]);
console.info('Added chunk data to results.data and rendered it with',
'results.jsList');
if (response.code === 200) {
console.group('corpus_analysis_query_results: Recieveing query data')
console.info('Client recieving query data via socket.on');
console.info('Recieved chunk', response);
/**
* Execute registered callbacks and notify View.
*/
client.socketEventListeners[type].executeCallbacks(response.payload);
console.info('Added chunk data to results.data.');
console.groupEnd();
} else {
console.group('corpus_analysis_query_results: Client failed recieving',
'the results via socket.on');
let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
console.error(`corpus_analysis_query: ${errorText}`);
console.groupEnd();
}
});
} else {
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.results.data, client]);
client.socketEventListeners[type].executeCallbacks();
console.groupEnd();
}
console.groupEnd();
}
// export listeners from this module
export { recieveSession, recieveQueryStatus, recieveQueryData };
export {
recieveConnected,
recieveMetaData,
recieveQueryStatus,
recieveQueryData
};

View File

@ -5,17 +5,15 @@
*/
class Results {
constructor(data, jsList , metaData) {
this.data = data;
this.jsList = jsList;
this.metaData = metaData
constructor() {
this.data = new Data();
this.metaData = new MetaData();
this.resultsData = new Data();
this.subResultsData = new Data();
console.info('Initialized the Results object.');
}
clearAll() {
this.jsList.clear();
this.jsList.update();
init() {
this.data.init();
this.metaData.init();
this.resultsData.init()
@ -28,7 +26,7 @@ class Results {
class Data {
// Sets empty object structure. Also usefull to delete old results.
// matchCount default is 0
init(matchCount = 0, type = "results") {
init(matchCount=0, type="results") {
this.matches = []; // list of all c with lc and rc
this.cpos_lookup = {}; // object contains all this key value pair
this.text_lookup = {}; // same as above for all text ids
@ -125,7 +123,7 @@ class Data {
class MetaData {
// Sets empty object structure when no input is given.
// if json object like input is given class fields are created from this
init(json = {}) {
init(json={}) {
Object.assign(this, json);
}
}

View File

@ -0,0 +1,73 @@
/**
* 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

@ -65,7 +65,102 @@
{% include 'modals/export_query_results.html.j2' %}
{% include 'modals/context_modal.html.j2' %}
<script type="text/javascript"
src="web/app/static/js/modules/corpus_analysis/main.js">
<!-- import modules -->
<script type="module">
/**
* First Phase:
* Document content is loaded and scripts are being imported and executed.
*/
import {
Client,
SocketEventListener,
ListenerCallback,
} from '../../static/js/modules/corpus_analysis/client/Client.js';
import {
recieveConnected,
recieveMetaData,
recieveQueryStatus,
recieveQueryData,
} from '../../static/js/modules/corpus_analysis/client/listeners.js';
import {
Results,
} from '../../static/js/modules/corpus_analysis/model/Results.js';
import {
prepareQueryData,
saveQueryData,
saveMetaData,
} from '../../static/js/modules/corpus_analysis/client/callbacks.js';
/**
* Second Phase:
* Asynchronus and event driven code
*/
document.addEventListener("DOMContentLoaded", () => {
// Initialize the client for server client communication in dynamic mode
let corpusId = {{ corpus_id }}
const client = new Client({'corpusId': corpusId,
'socket': nopaque.socket,
'logging': true,
'dynamicMode': true});
/**
* Initializing the results object as a model holding all the data of a query.
* Also holds the metadata of one query.
*/
let results = new Results();
/**
* Register listeners listening to socket.io events and their callbacks
* Afterwards load them.
*/
const listenForConnected = new SocketEventListener('corpus_analysis_init',
recieveConnected);
const listenForMetaData = new SocketEventListener('corpus_analysis_meta_data',
recieveMetaData);
const metaDataCallback = new ListenerCallback('corpus_analysis_meta_data',
saveMetaData,
[client, results]);
listenForMetaData.setCallbacks([metaDataCallback]);
const listenForQueryStatus = new SocketEventListener('corpus_analysis_query',
recieveQueryStatus);
const queryStatusCallback = new ListenerCallback('corpus_analysis_query',
prepareQueryData,
[client, results]);
listenForQueryStatus.setCallbacks([queryStatusCallback]);
const listenForQueryData = new SocketEventListener('corpus_analysis_query_results',
recieveQueryData);
const queryDataCallback = new ListenerCallback('corpus_analysis_query_results',
saveQueryData,
[client, results]);
listenForQueryData.setCallbacks([queryDataCallback]);
client.setSocketEventListeners([listenForConnected,
listenForQueryStatus,
listenForQueryData,
listenForMetaData]);
client.loadSocketEventListeners();
// Connect client to server
client.connect();
// Send a query and recieve its answer data
let queryFormElement = document.getElementById('query-form');
queryFormElement.addEventListener('submit', (event) => {
try {
/**
* Selects first page of result list if pagination is already available
* from an query submitted before.
* This avoids confusion for the user e.g.: The user was on page 24
* reviewing the results and issues a new query. He would not see any
* results until the new results reach page 24 or he clicks on another
* valid result page element from the new pagination.
*/
let firstPageElement = document.querySelector('a.page');
firstPageElement.click();
} catch (e) {
// No page element is present if first query is submitted.
}
// Prevent page from reloading on submit
event.preventDefault();
// Get query string and send query to server
results.data.getQueryStr(queryFormElement);
client.query(results.data.query);
});
});
</script>
{% endblock %}