Further rework

This commit is contained in:
Stephan Porada 2020-08-17 16:15:34 +02:00
parent f94d21fa96
commit b3e8976c1c
13 changed files with 755 additions and 348 deletions

View File

@ -5,12 +5,38 @@
* the server and recieves them. * the server and recieves them.
*/ */
class CorpusAnalysisClient { class CorpusAnalysisClient {
constructor(corpusId, socket, logging=false) { constructor({corpusId = null,
socket = null,
logging = true,
dynamicMode = true} = {}) {
this.corpusId = corpusId; this.corpusId = corpusId;
this.displays = {}; this.displays = {};
this.dynamicMode = dynamicMode;
this.logging = logging; this.logging = logging;
this.requestQueryProgress = 0;
this.results = undefined; // holds the results object later on
this.socket = socket; this.socket = socket;
this.socketEventListeners = {}; this.socketEventListeners = {};
/**
* Set client into imported mode if SOME THIGN IS INDICATING it
*
*/
// Disable all console logging. Credits to https://gist.github.com/kmonsoor/0244fdb4ad79a4826371e58a1a5fa984
if (!logging) {
(() => {
let console = (window.console = window.console || {});
[
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml',
'error', 'exception', 'group', 'groupCollapsed', 'groupEnd',
'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'table',
'time', 'timeEnd', 'timeStamp', 'trace', 'warn'
].forEach(method => {
console[method] = () => {};
});
})();
}
} }
// Registers one or more SocketEventListeners to the CorpusAnalysisClient. // Registers one or more SocketEventListeners to the CorpusAnalysisClient.
@ -31,10 +57,32 @@ class CorpusAnalysisClient {
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. MutlipleResults and atattchSomeCallback not yet implemented.
*/
getHTMLElements(arrayOfSelectors, multipleResults=false, atattchSomeCallback=false) {
for (let selector of arrayOfSelectors) {
let element = document.querySelector(selector);
let cleanKey = [];
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;
}
}
/** /**
* Requests a corpus analysis session via socket.io. * Requests a corpus analysis session via socket.io.
* Opens a loading modal at the start of the request * Opens a loading modal at the start of the request
* Should be a private method if ES2020 is finalized (Maybe?)
*/ */
requestSession() { requestSession() {
console.info('corpus_analysis_init: Client requesting session via', console.info('corpus_analysis_init: Client requesting session via',
@ -48,12 +96,11 @@ class CorpusAnalysisClient {
/** /**
* Sends the query string to the server. * Sends the query string to the server.
* Should be a private method if ES2020 is finalized (Maybe?)
*/ */
requestQueryData(queryStr) { requestQueryData(queryStr) {
console.info('corpus_analysis_query: Client requesting query data via', console.info('corpus_analysis_query: Client requesting query data via',
'socket.emit for the query', queryStr); 'socket.emit for the query', queryStr);
// TODO: Display stuff // TODO: Display stuff ?
this.socket.emit('corpus_analysis_query', queryStr); this.socket.emit('corpus_analysis_query', queryStr);
} }
} }

View File

@ -1,37 +1,68 @@
// This callback is called on socket.on "query".
// Does some hiding, showing disabling etc.
/**
* This call back is called when the SocketEventListener 'recieveQueryStatus'
* has been triggered. It just gets the incoming status data of the issued
* and does some preperation work like hiding or showing elements and deleting
* the date from the last query.
*/
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']);
client.requestQueryProgress = 0;
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%'
}
/** /**
* This callback is called when the SocketEventListener 'recieveQueryData' * This callback is called when the SocketEventListener 'recieveQueryData'
* has been triggered. It takes the incoming chunk and renders the results in a * has been triggered. It takes the incoming chunk and renders the results in a
* the results.jsList. It can eiterh hande llive incoming data or imported * the results.jsList. It can either handle live incoming data or imported
* results data. * results data.
*/ */
function queryRenderResults(payload, client, imported=false) { function queryRenderResults(payload, client) {
console.info("Current recieved chunk:", payload.chunk); client.getHTMLElements(['#recieved-match-count', '#match-count'])
if (payload.chunk.cpos_ranges == true) { const renderResults = (data) => {
client.results.data["cpos_ranges"] = true; /**
} else { * resultItem saves the incoming chunk matches as objects to add those later
client.results.data["cpos_ranges"] = false; * 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) {
* resultItem is a list where if (payload.chunk.cpos_ranges == true) {
*/ client.results.data['cpos_ranges'] = true;
let resultItems = []; } else {
// get infos for full match row client.results.data['cpos_ranges'] = false;
for (let [index, match] of payload.chunk.matches.entries()) { }
resultItems.push({...match, ...{"index": index + client.results.data.matches.length}}); renderResults(payload.chunk);
}
if (!imported) {
// update progress bar
// queryResultsDeterminateElement.style.width = `${payload.progress}%`;
client.results.jsList.add(resultItems, (items) => {
for (let item of items) {
item.elm = client.results.jsList.createResultRowElement(item, payload.chunk);
}
});
helperQueryRenderResults(payload, client); helperQueryRenderResults(payload, client);
// if (progress === 100) { console.info('Result progress is:', client.requestQueryProgress);
// resultsCreateElement.classList.remove("disabled"); if (client.requestQueryProgress === 100) {
// queryResultsProgressElement.classList.add("hide"); /**
// queryResultsUserFeedbackElement.classList.add("hide"); * activate, hide or show elements if all reults have been recieved
* also load some new elements taht have not ben loaded before
*/
client.getHTMLElements(['#query-results-create']);
client.queryProgressBar.classList.toggle('hide');
client.queryResultsUserFeedback.classList.toggle('hide');
client.queryResultsCreate.classList.toggle('disabled');
// resultsExportElement.classList.remove("disabled"); // resultsExportElement.classList.remove("disabled");
// addToSubResultsElement.removeAttribute("disabled"); // addToSubResultsElement.removeAttribute("disabled");
// // inital expert mode check and sub results activation // // inital expert mode check and sub results activation
@ -42,20 +73,18 @@ function queryRenderResults(payload, client, imported=false) {
// if (expertModeSwitchElement.checked) { // if (expertModeSwitchElement.checked) {
// client.results.jsList.expertModeOn("query-display"); // client.results.jsList.expertModeOn("query-display");
// } // }
// } }
} else if (imported) { } else if (!client.dynamicMode) {
client.results.jsList.add(resultItems, (items) => { client.requestQueryProgress === 100;
for (let item of items) { client.queryResultsUserFeedback.classList.toggle('hide');
item.elm = client.results.jsList.createResultRowElement(item, payload.chunk, renderResults(payload);
true); helperQueryRenderResults({'chunk': payload}, client);
} client.queryProgressBar.classList.toggle('hide');
helperQueryRenderResults(payload, client); client.queryResultsUserFeedback.classList.toggle('hide');
progress = 100; client.results.jsList.activateInspect();
client.results.jsList.activateInspect(); if (expertModeSwitchElement.checked) {
if (expertModeSwitchElement.checked) { client.results.jsList.expertModeOn("query-display");
client.results.jsList.expertModeOn("query-display"); }
}
});
} }
} }
@ -65,20 +94,23 @@ function helperQueryRenderResults (payload, client) {
client.results.jsList.changeContext(); // sets lr context on first result load client.results.jsList.changeContext(); // sets lr context on first result load
// incorporating new chunk results into full results // incorporating new chunk results into full results
client.results.data.matches.push(...payload.chunk.matches); client.results.data.matches.push(...payload.chunk.matches);
client.results.data.addData(payload.chunk.cpos_lookup, "cpos_lookup"); client.results.data.addData(payload.chunk.cpos_lookup, 'cpos_lookup');
client.results.data.addData(payload.chunk.text_lookup, "text_lookup"); client.results.data.addData(payload.chunk.text_lookup, 'text_lookup');
// complete metaData // complete metaData
// client.results.metaData.add(); // client.results.metaData.add();
// show user current and total match count // show user current and total match count
// receivedMatchCountElement.innerText = `${client.results.data.matches.length}`; client.recievedMatchCount.textContent = `${client.results.data.matches.length}`;
// textLookupCountElement.innerText = `${Object.keys(client.results.data.text_lookup).length}`; client.textLookupCount.textContent = `${Object.keys(client.results.data.text_lookup).length}`;
let titles = new Array(); let titles = new Array();
for (let [key, value] of Object.entries(client.results.data.text_lookup)) { for (let [key, value] of Object.entries(client.results.data.text_lookup)) {
titles.push(`${value.title} (${value.publishing_year})`); titles.push(`${value.title} (${value.publishing_year})`);
}; };
// textTitlesElement.innerText = `${titles.join(", ")}`; client.textLookupTitles.textContent = `${titles.join(", ")}`;
// upate progress status // update progress bar and requestQueryProgress
// progress = payload.progress; // global declaration client.queryProgressBar.lastElementChild.style.width = `${payload.progress}%`;
client.requestQueryProgress = payload.progress;
} }
export { queryRenderResults } // TODO: Add data to data objekt using its own socket on event?
export { querySetup, queryRenderResults }

View File

@ -1,4 +1,4 @@
import {queryRenderResults} from './nopaque.listenerCallbacks.js' import { querySetup, queryRenderResults } from './nopaque.listenerCallbacks.js'
/** /**
* Recieves a corpus analysis session via socket.io. * Recieves a corpus analysis session via socket.io.
@ -7,13 +7,18 @@ import {queryRenderResults} from './nopaque.listenerCallbacks.js'
*/ */
function recieveSession(client) { function recieveSession(client) {
client.socket.on('corpus_analysis_init', (response) => { client.socket.on('corpus_analysis_init', (response) => {
/**
* Check if request for session was ok.
* If OK execute callbacks and hide/show displays.
*/
console.group('recieve session') console.group('recieve session')
console.info('corpus_analysis_init: Client recieving session/or error',
'codes via socket.on');
if (response.code === 200) { if (response.code === 200) {
console.info('corpus_analysis_init: Client recieving session/or error',
'codes via socket.on');
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);
console.groupEnd(); // Handling hide/show of displays
if (client.displays.init != undefined) { if (client.displays.init != undefined) {
client.displays.init.element.M_Modal.close(); client.displays.init.element.M_Modal.close();
client.displays.init.setVisibilityByStatus('success'); client.displays.init.setVisibilityByStatus('success');
@ -29,6 +34,7 @@ function recieveSession(client) {
} }
console.error(`corpus_analysis_init: ${errorText}`); console.error(`corpus_analysis_init: ${errorText}`);
} }
console.groupEnd();
}); });
} }
@ -40,10 +46,36 @@ function recieveSession(client) {
*/ */
function recieveQueryStatus(client) { function recieveQueryStatus(client) {
client.socket.on('corpus_analysis_query', (response) => { client.socket.on('corpus_analysis_query', (response) => {
/**
* Check if issued query was ok.
* If OK execute callbacks and hide/show displays.
*/
console.group('corpus_analysis_query: Client recieving query process', console.group('corpus_analysis_query: Client recieving query process',
'status via socket.on'); 'status via socket.on');
client.results.clearAll(); if (response.code === 200) {
console.info(response); 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 callbacks
querySetup(response.payload, client);
} else {
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();
}); });
} }
@ -52,15 +84,21 @@ function recieveQueryStatus(client) {
* Recieves the query data from the request and handles it. * Recieves the query data from the request and handles it.
*/ */
function recieveQueryData(client) { function recieveQueryData(client) {
client.socket.on('corpus_analysis_query_results', (response) => { console.group('corpus_analysis_query_results: Client recieving or loading',
console.group('corpus_analysis_query_results: Client recieving query', 'query data.');
'data via socket.on'); if (client.dynamicMode) {
console.info('Client recieving query data via socket.on');
client.socket.on('corpus_analysis_query_results', (response) => {
console.info('Recieved chunk', response); console.info('Recieved chunk', response);
queryRenderResults(response.payload, client); queryRenderResults(response.payload, client);
console.info('Added chunk data to results.data and rendered it with', console.info('Added chunk data to results.data and rendered it with',
'results.jsList') 'results.jsList');
console.groupEnd(); });
}); } else {
console.info('Client loading imported query data from database.');
queryRenderResults(client.results.data, client);
}
console.groupEnd();
} }
// export listeners from this module // export listeners from this module

View File

@ -23,12 +23,12 @@ class Data {
// Sets empty object structure. Also usefull to delete old results. // Sets empty object structure. Also usefull to delete old results.
// matchCount default is 0 // 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.matches = []; // list of all c with lc and rc
this["cpos_lookup"] = {}; // object contains all this key value pair this.cpos_lookup = {}; // object contains all this key value pair
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["query"] = ""; this.query = '';
} }
addData(jsonData, key=null) { addData(jsonData, key=null) {
@ -45,7 +45,7 @@ class Data {
let queryFormData; let queryFormData;
let queryStr; let queryStr;
queryFormData = new FormData(queryFormElement); queryFormData = new FormData(queryFormElement);
queryStr = queryFormData.get("query-form-query"); queryStr = queryFormData.get('query-form-query');
this["query"] = queryStr; this["query"] = queryStr;
} }
@ -72,7 +72,7 @@ class Data {
// Function to download data as Blob created from string // Function to download data as Blob created from string
// should be private but that is not yet a feature of javascript 08.04.2020 // should be private but that is not yet a feature of javascript 08.04.2020
download(downloadElement, dataStr, filename, type, filenameSlug) { download(downloadElement, dataStr, filename, type, filenameSlug) {
console.log("Start Download!"); console.log('Start Download!');
let file; let file;
filename += filenameSlug; filename += filenameSlug;
file = new Blob([dataStr], {type: type}); file = new Blob([dataStr], {type: type});

View File

@ -0,0 +1,83 @@
/**
* HTML for showing infos about the current query or result. Also gives
* the user the abiltiy to access the meta data for the current query or
* result.
*/
const template = document.createElement('template');
template.innerHTML = `
<link rel="stylesheet" href="../../static/fonts/Material_design_icons/material-icons.css">
<link rel="stylesheet" href="../../static/css/Materialize/materialize.min.css">
<link rel="stylesheet" href="../../static/css/nopaque.css">
<div class="col">
<h6 style="margin-top: 0px;">Infos</h6>
<div class="divider" style="margin-bottom: 10px;"></div>
<div class="row">
<div class="col s12">
<button id="show-metadata"
class="waves-effect
waves-light
btn-flat
flat-interaction"
type="submit">Corpus Metadata
<i class="material-icons left">info_outline</i>
</button>
</div>
<div class="col s12">
<p>
<slot name="received-match-count">
0
</slot> of
<slot name="match-count">(to be determined)</slot>
matches loaded.
<br>
Matches occured in
<slot name="text-lookup-count">(to be determined)</slot>
corpus files:
<br>
<slot name=text-titles>(to be determined)</slot>
</p>
<p id="query-results-user-feedback">
<i class="material-icons">help</i>
The Server is still sending your results.
Functions like "Export Results" and "Match Inspect" will be
available after all matches have been loaded.
</p>
<div class="progress" id="query-progress-bar">
<div class="determinate"></div>
</div>
</div>
</div>
</div>
`;
class InfoMenu extends HTMLElement {
constructor() {
super();
this.appendChild(template.content.cloneNode(true));
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
// methods that will be used in connectedCallback on eventListeners
showMetadata() {
console.log('Show metadata somehow');
}
connectedCallback() {
const showMetadataBtn = this.querySelector('#show-metadata');
showMetadataBtn.addEventListener('click', () => this.showMetadata());
}
disconnectedCallback() {
const showMetadataBtn = this.querySelector('#show-metadata');
showMetadataBtn.removeEventListener();
}
}
window.customElements.define('info-menu', InfoMenu);
export { InfoMenu };

View File

@ -67,89 +67,93 @@
<!-- import modules --> <!-- import modules -->
<script type="module"> <script type="module">
/** /**
* First Phase: * First Phase:
* document content is loaded and scripts are being imported and executed * document content is loaded and scripts are being imported and executed
*/ */
import {CorpusAnalysisClient, import { CorpusAnalysisClient,
CorpusAnalysisDisplay, CorpusAnalysisDisplay,
SocketEventListener} from '../../static/js/modules/nopaque.CorpusAnalysisClient.js'; SocketEventListener } from '../../static/js/modules/nopaque.CorpusAnalysisClient.js';
import {recieveSession, recieveQueryStatus, import { recieveSession, recieveQueryStatus,
recieveQueryData} from '../../static/js/modules/nopaque.listenerFunctions.js'; recieveQueryData } from '../../static/js/modules/nopaque.listenerFunctions.js';
import {Results, Data, MetaData} from '../../static/js/nopaque.Results.js'; import { Results, Data, MetaData } from '../../static/js/nopaque.Results.js';
import {ResultsList} from '../../static/js/nopaque.lists.js'; import { ResultsList } from '../../static/js/nopaque.lists.js';
/**
* Second Phase:
* Asynchronus and event driven code
*/
document.addEventListener("DOMContentLoaded", () => {
// Initialize the CorpusAnalysisClient
let corpusId = {{ corpus_id}}
const client = new CorpusAnalysisClient({'corpusId': corpusId,
'socket': nopaque.socket,
'logging': true,
'dynamicMode': true});
console.info("CorpusAnalysisClient created as client:", client);
// Initialize modals which are shown depending on events or client status
const initLoadingElement = document.getElementById("init-display");
const initLoadingModal = M.Modal.init(initLoadingElement,
{"dismissible": false});
// Set up display elements which hare show depending on the client status
const initLoadingDisplay = new CorpusAnalysisDisplay(initLoadingModal);
client.getHTMLElements(['#query-display']);
const queryDisplay = new CorpusAnalysisDisplay(client.queryDisplay);
// Register those display elements to client
client.setDisplay("init", initLoadingDisplay);
client.setDisplay("query", queryDisplay);
/** /**
* Second Phase: * Initializing the results object holding all the data of a query.
* Asynchronus and event driven code * Also holds the metadata of one query.
* resultsListOptions is set to determine how many results per page are
* shown etc.
* Lastly it contains the object ResultsList which is a list.js
* subclass which handles the visual representation of the query data.
*/ */
document.addEventListener("DOMContentLoaded", () => { let displayOptionsData = ResultsList.getDisplayOptions('display-options-form');
// Initialize the CorpusAnalysisClient ResultsList.options.page = displayOptionsData["resultsPerPage"];
const client = new CorpusAnalysisClient({{ corpus_id }}, nopaque.socket); let data = new Data();
console.info("CorpusAnalysisClient created as client:", client); let resultsList = new ResultsList("result-list", ResultsList.options);
// Initialize modals which are shown depending on events or client status let resultsMetaData = new MetaData();
const initLoadingElement = document.getElementById("init-display"); let results = new Results(data, resultsList, resultsMetaData);
const initLoadingModal = M.Modal.init(initLoadingElement, // make results part of the client
{"dismissible": false}); client.results = results;
// Set up display elements which hare show depending on the client status console.info('Initialized the Results object.')
const initLoadingDisplay = new CorpusAnalysisDisplay(initLoadingModal); // register listeners listening to socket.io events and load them
// Register those display elements to client const listenForSession = new SocketEventListener('corpus_analysis_init',
client.setDisplay("init", initLoadingDisplay); recieveSession);
/** const listenForQueryStatus = new SocketEventListener('corpus_analysis_query',
* Initializing the results object holding all the data of a query. recieveQueryStatus);
* Also holds the metadata of one query. const listenForQueryData = new SocketEventListener('corpus_analysis_query_results',
* resultsListOptions is set to determine how many results per apge are recieveQueryData);
* shown etc. client.setSocketEventListeners([listenForSession, listenForQueryStatus,
* Lastly it also contains the object ResultsList which is a list.js listenForQueryData]);
* subclass which handles the visual reprensetnation of the query data. client.loadSocketEventListeners();
*/ // Session initialization
let displayOptionsData = ResultsList.getDisplayOptions('display-options-form'); client.requestSession();
ResultsList.options.page = displayOptionsData["resultsPerPage"]; // Send a query and recieve its answer data
let resultsList = new ResultsList("result-list", ResultsList.options); let queryFormElement = document.getElementById("query-form");
let resultsMetaData = new MetaData(); queryFormElement.addEventListener("submit", (event) => {
let results = new Results(new Data(), resultsList, resultsMetaData); try {
// make results part of the client /**
client.results = results; * Selects first page of result list if pagination is already available
console.info('Initialized the Results object.') * from an query submitted before.
// register listeners listening to socket.io events and load them * This avoids confusion for the user e.g.: The user was on page 24
const listenForSession = new SocketEventListener('corpus_analysis_init', * reviewing the results and issues a new query. He would not see any
recieveSession); * results until the new results reach page 24 or he clicks on another
const listenForQueryStatus = new SocketEventListener('corpus_analysis_query', * valid result page element from the new pagination.
recieveQueryStatus); */
const listenForQueryData = new SocketEventListener('corpus_analysis_query_results', let firstPageElement = document.querySelector('a.page');
recieveQueryData); firstPageElement.click();
client.setSocketEventListeners([listenForSession, listenForQueryStatus, } catch (e) {
listenForQueryData]); // No page element is present if first query is submitted.
client.loadSocketEventListeners(); }
// Session initialization // Prevent page from reloading on submit
client.requestSession(); event.preventDefault();
// Send a query and recieve its answer data // Get query string and send query to server
let queryFormElement = document.getElementById("query-form"); results.data.getQueryStr(queryFormElement);
queryFormElement.addEventListener("submit", (event) => { client.requestQueryData(results.data.query);
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 xpath = '//a[@class="page" and text()=1]';
let firstPageElement = document.evaluate(xpath,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null).singleNodeValue;
firstPageElement.click();
} catch (e) {
}
// Prevent page from reloading on submit
event.preventDefault();
// Get query string and send query to server
results.data.getQueryStr(queryFormElement);
client.requestQueryData(results.data.query);
});
}); });
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -107,8 +107,8 @@
</div> </div>
</div> </div>
<script type="module">
<script> import {RessourceList} from '../../static/js/nopaque.lists.js';
class InformationUpdater { class InformationUpdater {
constructor(corpusId, foreignCorpusFlag) { constructor(corpusId, foreignCorpusFlag) {
this.corpusId = corpusId; this.corpusId = corpusId;
@ -196,7 +196,8 @@
nopaque.socket.emit("foreign_user_data_stream_init", {{ corpus.user_id }}); nopaque.socket.emit("foreign_user_data_stream_init", {{ corpus.user_id }});
}); });
{% endif %} {% endif %}
var corpusFilesList = new RessourceList("corpus-files", null, "CorpusFile");
let corpusFilesList = new RessourceList("corpus-files", null, "CorpusFile");
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
corpusFilesList._add({{ corpus_files|tojson|safe }}); corpusFilesList._add({{ corpus_files|tojson|safe }});
}); });

View File

@ -12,7 +12,7 @@ the selected sub results.-->
disabled disabled
flat-interaction" flat-interaction"
type="submit" type="submit"
id="results-create">Create Results id="query-results-create">Create Results
<i class="material-icons left">build</i> <i class="material-icons left">build</i>
</button> </button>
<button id="query-results-export" <button id="query-results-export"

View File

@ -17,29 +17,27 @@ result.-->
</button> </button>
</div> </div>
<div class="col s12"> <div class="col s12">
<div class="progress hide" id="query-progress-bar">
<div class="determinate"></div>
</div>
<p> <p>
<span id="received-match-count"> <span id="recieved-match-count">
</span> of </span> of
<span id="match-count"></span> <span id="total-match-count"></span>
matches loaded. matches loaded.
<br> <br>
Matches occured in Matches occured in
<span id="text-lookup-count"></span> <span id="text-lookup-count"></span>
corpus files: corpus files:
<br> <br>
<span id=text-titles></span> <span id=text-lookup-titles></span>
</p> </p>
{% if not imported %} <p class="hide" id="query-results-user-feedback">
<p id="query-results-user-feedback"> <i class="material-icons tiny">help</i>
<i class="material-icons">help</i> Server is sending your results.
The Server is still sending your results.
Functions like "Export Results" and "Match Inspect" will be Functions like "Export Results" and "Match Inspect" will be
available after all matches have been loaded. available after all matches have been loaded.
</p> </p>
<div class="progress" id="query-results-progress">
<div class="determinate" id="query-results-determinate"></div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -150,11 +150,11 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module">
<script> import {RessourceList} from '../../static/js/nopaque.lists.js';
var corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus"); let corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus");
var jobList = new RessourceList("jobs", nopaque.jobsSubscribers, "Job"); let jobList = new RessourceList("jobs", nopaque.jobsSubscribers, "Job");
var queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult"); let queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult");
</script> </script>
{% endblock %} {% endblock %}

View File

@ -54,180 +54,150 @@
{% include 'modals/context_modal.html.j2' %} {% include 'modals/context_modal.html.j2' %}
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}"> <script type="module">
</script> /**
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}"> * First Phase:
</script> * document content is loaded and scripts are being imported and executed
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}"> */
</script> import { CorpusAnalysisClient,
<script> CorpusAnalysisDisplay,
// ###### global variables ###### SocketEventListener } from '../../static/js/modules/nopaque.CorpusAnalysisClient.js';
var full_result_json; import { recieveSession, recieveQueryStatus,
var result_json; recieveQueryData } from '../../static/js/modules/nopaque.listenerFunctions.js';
var receivedMatchCountElement; // Nr. of loaded matches will be displayed in this element import { Results, Data, MetaData } from '../../static/js/nopaque.Results.js';
var textLookupCountElement // Nr of texts the matches occured in will be shown in this element import { ResultsList } from '../../static/js/nopaque.lists.js';
var textTitlesElement; // matched text titles import { queryRenderResults, querySetup } from '../../static/js/modules/nopaque.listenerCallbacks.js'
var progress; // global progress value
var queryResultsProgressElement; // Div element holding the progress bar
var expertModeSwitchElement; // Expert mode switch Element
var matchCountElement; // Total nr. of matches will be displayed in this element
var interactionElements; // Interaction elements and their parameters
var contextModal; // Modal to open on inspect for further match context
// ###### Defining local scope variables /**
let displayOptionsFormElement; // Form holding the display informations * Second Phase:
let resultItems; // array of built html result items row element. This is called when results are transmitted and being recieved * Asynchronus and event driven code
let hitsPerPageInputElement;let contextPerItemElement; // Form Element for display option */
let paginationElements; document.addEventListener("DOMContentLoaded", () => {
let inspectBtnElements; // Initialize the CorpusAnalysisClient
let metaDataModal; const client = new CorpusAnalysisClient({'logging': true,
let showMetaDataButton 'dynamicMode': false});
console.info("CorpusAnalysisClient created as client:", client);
// Set up display elements which hare show depending on the client status
client.getHTMLElements(['#query-display']);
const queryDisplay = new CorpusAnalysisDisplay(client.queryDisplay);
// Register those display elements to client
client.setDisplay("query", queryDisplay);
/**
* Initializing the results object holding all the data of a query.
* Also holds the metadata of one query.
* resultsListOptions is set to determine how many results per page are
* shown etc.
* Lastly it contains the object ResultsList which is a list.js
* subclass which handles the visual representation of the query data.
*/
let displayOptionsData = ResultsList.getDisplayOptions('display-options-form');
ResultsList.options.page = displayOptionsData["resultsPerPage"];
let data = new Data();
let resultsList = new ResultsList("result-list", ResultsList.options);
let resultsMetaData = new MetaData();
let results = new Results(data, resultsList, resultsMetaData);
// make results part of the client
client.results = results;
// inits some object keys and values
client.results.clearAll();
console.info('Initialized the Results object.')
// init some modals
let deleteOverlay = () => {
let overlay = document.getElementsByClassName("modal-overlay")[0];
overlay.remove();
};
client.getHTMLElements(['#meta-data-modal'])
client.metaDataModal = M.Modal.init(client.metaDataModal,
{'preventScrolling': false,
'opacity': 0.0,
'dismissible': false,
'onOpenEnd': deleteOverlay});
// saving imported data into client object
const payload = {{ query_result_file_content|tojson|safe }};
// ###### Initializing variables ######
displayOptionsFormElement = document.getElementById("display-options-form");
resultItems = [];
receivedMatchCountElement = document.getElementById("received-match-count");
textLookupCountElement = document.getElementById("text-lookup-count");
textTitlesElement = document.getElementById("text-titles");
queryResultsProgressElement = document.getElementById("query-results-progress");
expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
matchCountElement = document.getElementById("match-count");
hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
contextPerItemElement = document.getElementById("display-options-form-result_context");
paginationElements = document.getElementsByClassName("pagination");
contextModal = document.getElementById("context-modal");
metaDataModal = document.getElementById("meta-data-modal");
showMetaDataButton = document.getElementById("show-metadata");
// js list options //
displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement); // // Initialization of interactionElemnts
resultsListOptions = {page: displayOptionsData["resultsPerPage"], // // An interactionElement is an object identifing a switch or button via
pagination: [{ // // htmlID. Callbacks are set for these elements which will be triggered on
name: "paginationTop", // // a pagination interaction by the user or if the status of the element has
paginationClass: "paginationTop", // // been altered. (Like the switche has ben turned on or off).
innerWindow: 8, // interactionElements = new InteractionElements();
outerWindow: 1 // let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
}, { // expertModeInteraction.setCallback("on",
paginationClass: "paginationBottom", // results.jsList.expertModeOn,
innerWindow: 8, // results.jsList,
outerWindow: 1 // ["query-display"])
}], // expertModeInteraction.setCallback("off",
valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}], // results.jsList.expertModeOff,
item: `<span></span>` // results.jsList,
}; // ["query-display"])
//
// let activateInspectInteraction = new InteractionElement("inspect",
// false);
// activateInspectInteraction.setCallback("noCheck",
// results.jsList.activateInspect,
// results.jsList);
//
// let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
// false);
// changeContextInteraction.setCallback("noCheck",
// results.jsList.changeContext,
// results.jsList)
// interactionElements.addInteractions([expertModeInteraction, activateInspectInteraction, changeContextInteraction]);
//
// // checks if a change for every interactionElement happens and executes
// // the callbacks accordingly
// interactionElements.onChangeExecute();
//
// // eventListener if pagination is used to apply new context size to new page
// // and also activate inspect match if progress is 100
// // also adds more interaction buttons like add to sub results
// for (let element of paginationElements) {
// element.addEventListener("click", (event) => {
// results.jsList.pageChangeEventInteractionHandler(interactionElements);
// });
// }
//
// render results directly with callbacks because we are not in dynamic mode
querySetup(payload, client);
queryRenderResults(payload, client);
document.addEventListener("DOMContentLoaded", () => { // // ### Show corpus Metadata
// Initialize some Modals // showMetaDataButton.onclick = () => {
contextModal = M.Modal.init(contextModal, {"dismissible": true}); // metaDataModal.open();
// };
// ###### recreating chunk structure to reuse callback queryRenderResults() //
full_result_json = {{ query_result_file_content|tojson|safe }}; // // live update of hits per page if hits per page value is changed
result_json = {}; // let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
result_json["chunk"] = {}; // hitsPerPageInputElement.onchange = changeHitsPerPageBind;
result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup; //
result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges; // // live update of lr context per item if context value is changed
result_json.chunk["matches"] = full_result_json.matches; // contextPerItemElement.onchange = results.jsList.changeContext;
result_json.chunk["text_lookup"] = full_result_json.text_lookup; //
// // new insepct event listener makeing use of javascript bubbleing
// Init corpus analysis components // let resultsTable = document.getElementById("query-results");
data = new Data(); // resultsTable.addEventListener("click", (event) => {
resultsList = new ResultsList("result-list", resultsListOptions); // if (event.target.classList.contains("inspect-btn")) {
resultsMetaData = new MetaData(); // const dataIndex = event.target.closest("tr").dataset.index;
results = new Results(data, resultsList, resultsMetaData); // const fakeResponse = results.jsList.createFakeResponse();
results.clearAll(); // inits some object keys and values // results.jsList.showMatchContext(fakeResponse);
// init some modals // }
let deleteOverlay = () => { // });
let overlay = document.getElementsByClassName("modal-overlay")[0]; //
overlay.remove(); // // scroll to top button if user scrolled down the list
}; // let headline = document.querySelector(".headline");
metaDataModal = M.Modal.init(metaDataModal, {"preventScrolling": false, // let scrollToTop = document.querySelector("#menu-scroll-to-top-div");
"opacity": 0.0, // window.addEventListener("scroll", (event) => {
"dismissible": false, // if (pageYOffset > 250) {
"onOpenEnd": deleteOverlay}); // scrollToTop.classList.toggle("hide", false);
// } else {
// setting some initial values for user feedback // scrollToTop.classList.toggle("hide", true);
matchCountElement.innerText = full_result_json.match_count; // }
// });
// Initialization of interactionElemnts // scrollToTop.onclick = () => {
// An interactionElement is an object identifing a switch or button via // headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
// htmlID. Callbacks are set for these elements which will be triggered on // };
// a pagination interaction by the user or if the status of the element has });
// been altered. (Like the switche has ben turned on or off).
interactionElements = new InteractionElements();
let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
expertModeInteraction.setCallback("on",
results.jsList.expertModeOn,
results.jsList,
["query-display"])
expertModeInteraction.setCallback("off",
results.jsList.expertModeOff,
results.jsList,
["query-display"])
let activateInspectInteraction = new InteractionElement("inspect",
false);
activateInspectInteraction.setCallback("noCheck",
results.jsList.activateInspect,
results.jsList);
let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
false);
changeContextInteraction.setCallback("noCheck",
results.jsList.changeContext,
results.jsList)
interactionElements.addInteractions([expertModeInteraction, activateInspectInteraction, changeContextInteraction]);
// checks if a change for every interactionElement happens and executes
// the callbacks accordingly
interactionElements.onChangeExecute();
// eventListener if pagination is used to apply new context size to new page
// and also activate inspect match if progress is 100
// also adds more interaction buttons like add to sub results
for (let element of paginationElements) {
element.addEventListener("click", (event) => {
results.jsList.pageChangeEventInteractionHandler(interactionElements);
});
}
// render results in table imported parameter is true
queryRenderResults(result_json, true);
// ### Show corpus Metadata
showMetaDataButton.onclick = () => {
metaDataModal.open();
};
// live update of hits per page if hits per page value is changed
let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
hitsPerPageInputElement.onchange = changeHitsPerPageBind;
// live update of lr context per item if context value is changed
contextPerItemElement.onchange = results.jsList.changeContext;
// new insepct event listener makeing use of javascript bubbleing
let resultsTable = document.getElementById("query-results");
resultsTable.addEventListener("click", (event) => {
if (event.target.classList.contains("inspect-btn")) {
const dataIndex = event.target.closest("tr").dataset.index;
const fakeResponse = results.jsList.createFakeResponse();
results.jsList.showMatchContext(fakeResponse);
}
});
// scroll to top button if user scrolled down the list
let headline = document.querySelector(".headline");
let scrollToTop = document.querySelector("#menu-scroll-to-top-div");
window.addEventListener("scroll", (event) => {
if (pageYOffset > 250) {
scrollToTop.classList.toggle("hide", false);
} else {
scrollToTop.classList.toggle("hide", true);
}
});
scrollToTop.onclick = () => {
headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
};
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,233 @@
{% extends "nopaque.html.j2" %}
{% set headline = ' ' %}
{% set full_width = True %}
{% set imported = True %}
{% block page_content %}
<div class="col s12">
<div class="card">
<div class="card-content" style="padding-top: 5px;
padding-bottom: 0px;">
<!-- Query form -->
<div class="row">
<form id="query-form">
<div class="col s12 m10">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input disabled value="{{ query_metadata.query|escape }}" id="disabled" type="text" class="validate">
<label for="disabled">Query</label>
</div>
</div>
<div class="col s12 m2 right-align">
<br class="hide-on-small-only">
</div>
</form>
</div>
</div>
</div>
</div>
<!-- entire results div/card -->
<div class="col s12" id="query-display">
<div class="card">
<div class="card-content" id="result-list" style="overflow: hidden;">
<div class=" row show-on-success">
{% include 'interactions/infos.html.j2' %}
{% include 'interactions/display.html.j2' %}
{% include 'interactions/analysis.html.j2' %}
{% include 'interactions/cite.html.j2' %}
</div>
{% include 'tables/query_results.html.j2' %}
</div>
</div>
</div>
<!-- Scroll to top element -->
{% include 'interactions/scroll_to_top.html.j2' %}
<!-- Modals -->
{% include 'modals/show_metadata.html.j2' %}
{% include 'modals/show_text_details.html.j2' %}
{% include 'modals/context_modal.html.j2' %}
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
</script>
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
</script>
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}">
</script>
<script>
// ###### global variables ######
var full_result_json;
var result_json;
var receivedMatchCountElement; // Nr. of loaded matches will be displayed in this element
var textLookupCountElement // Nr of texts the matches occured in will be shown in this element
var textTitlesElement; // matched text titles
var progress; // global progress value
var queryResultsProgressElement; // Div element holding the progress bar
var expertModeSwitchElement; // Expert mode switch Element
var matchCountElement; // Total nr. of matches will be displayed in this element
var interactionElements; // Interaction elements and their parameters
var contextModal; // Modal to open on inspect for further match context
// ###### Defining local scope variables
let displayOptionsFormElement; // Form holding the display informations
let resultItems; // array of built html result items row element. This is called when results are transmitted and being recieved
let hitsPerPageInputElement;let contextPerItemElement; // Form Element for display option
let paginationElements;
let inspectBtnElements;
let metaDataModal;
let showMetaDataButton
// ###### Initializing variables ######
displayOptionsFormElement = document.getElementById("display-options-form");
resultItems = [];
receivedMatchCountElement = document.getElementById("received-match-count");
textLookupCountElement = document.getElementById("text-lookup-count");
textTitlesElement = document.getElementById("text-titles");
queryResultsProgressElement = document.getElementById("query-results-progress");
expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
matchCountElement = document.getElementById("match-count");
hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
contextPerItemElement = document.getElementById("display-options-form-result_context");
paginationElements = document.getElementsByClassName("pagination");
contextModal = document.getElementById("context-modal");
metaDataModal = document.getElementById("meta-data-modal");
showMetaDataButton = document.getElementById("show-metadata");
// js list options
displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
resultsListOptions = {page: displayOptionsData["resultsPerPage"],
pagination: [{
name: "paginationTop",
paginationClass: "paginationTop",
innerWindow: 8,
outerWindow: 1
}, {
paginationClass: "paginationBottom",
innerWindow: 8,
outerWindow: 1
}],
valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
item: `<span></span>`
};
document.addEventListener("DOMContentLoaded", () => {
// Initialize some Modals
contextModal = M.Modal.init(contextModal, {"dismissible": true});
// ###### recreating chunk structure to reuse callback queryRenderResults()
full_result_json = {{ query_result_file_content|tojson|safe }};
result_json = {};
result_json["chunk"] = {};
result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup;
result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges;
result_json.chunk["matches"] = full_result_json.matches;
result_json.chunk["text_lookup"] = full_result_json.text_lookup;
// Init corpus analysis components
data = new Data();
resultsList = new ResultsList("result-list", resultsListOptions);
resultsMetaData = new MetaData();
results = new Results(data, resultsList, resultsMetaData);
results.clearAll(); // inits some object keys and values
// init some modals
let deleteOverlay = () => {
let overlay = document.getElementsByClassName("modal-overlay")[0];
overlay.remove();
};
metaDataModal = M.Modal.init(metaDataModal, {"preventScrolling": false,
"opacity": 0.0,
"dismissible": false,
"onOpenEnd": deleteOverlay});
// setting some initial values for user feedback
matchCountElement.innerText = full_result_json.match_count;
// Initialization of interactionElemnts
// An interactionElement is an object identifing a switch or button via
// htmlID. Callbacks are set for these elements which will be triggered on
// a pagination interaction by the user or if the status of the element has
// been altered. (Like the switche has ben turned on or off).
interactionElements = new InteractionElements();
let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
expertModeInteraction.setCallback("on",
results.jsList.expertModeOn,
results.jsList,
["query-display"])
expertModeInteraction.setCallback("off",
results.jsList.expertModeOff,
results.jsList,
["query-display"])
let activateInspectInteraction = new InteractionElement("inspect",
false);
activateInspectInteraction.setCallback("noCheck",
results.jsList.activateInspect,
results.jsList);
let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
false);
changeContextInteraction.setCallback("noCheck",
results.jsList.changeContext,
results.jsList)
interactionElements.addInteractions([expertModeInteraction, activateInspectInteraction, changeContextInteraction]);
// checks if a change for every interactionElement happens and executes
// the callbacks accordingly
interactionElements.onChangeExecute();
// eventListener if pagination is used to apply new context size to new page
// and also activate inspect match if progress is 100
// also adds more interaction buttons like add to sub results
for (let element of paginationElements) {
element.addEventListener("click", (event) => {
results.jsList.pageChangeEventInteractionHandler(interactionElements);
});
}
// render results in table imported parameter is true
queryRenderResults(result_json, true);
// ### Show corpus Metadata
showMetaDataButton.onclick = () => {
metaDataModal.open();
};
// live update of hits per page if hits per page value is changed
let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
hitsPerPageInputElement.onchange = changeHitsPerPageBind;
// live update of lr context per item if context value is changed
contextPerItemElement.onchange = results.jsList.changeContext;
// new insepct event listener makeing use of javascript bubbleing
let resultsTable = document.getElementById("query-results");
resultsTable.addEventListener("click", (event) => {
if (event.target.classList.contains("inspect-btn")) {
const dataIndex = event.target.closest("tr").dataset.index;
const fakeResponse = results.jsList.createFakeResponse();
results.jsList.showMatchContext(fakeResponse);
}
});
// scroll to top button if user scrolled down the list
let headline = document.querySelector(".headline");
let scrollToTop = document.querySelector("#menu-scroll-to-top-div");
window.addEventListener("scroll", (event) => {
if (pageYOffset > 250) {
scrollToTop.classList.toggle("hide", false);
} else {
scrollToTop.classList.toggle("hide", true);
}
});
scrollToTop.onclick = () => {
headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
};
});
</script>
{% endblock %}

View File

@ -83,8 +83,9 @@
</div> </div>
</div> </div>
<script> <script type="module">
var corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus"); import {RessourceList} from '../../static/js/nopaque.lists.js';
var queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult"); let corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus");
let queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult");
</script> </script>
{% endblock %} {% endblock %}