Corpus analysis version 3.0 lul

This commit is contained in:
Stephan Porada 2020-04-01 13:44:06 +02:00
parent 90cf1bf8a0
commit 46b82f5737
9 changed files with 529 additions and 62 deletions

View File

@ -67,7 +67,7 @@ def pj_corpus_analysis_query(query):
socketio.emit('pj_corpus_analysis_query', response, room=request.sid) socketio.emit('pj_corpus_analysis_query', response, room=request.sid)
return return
response = {'code': 200, 'desc': None, 'msg': 'OK', response = {'code': 200, 'desc': None, 'msg': 'OK',
'payload': {**query_status, 'num_matches': results.size}} 'payload': {**query_status, 'match_count': results.size}}
socketio.emit('pj_corpus_analysis_query', response, room=request.sid) socketio.emit('pj_corpus_analysis_query', response, room=request.sid)
chunk_size = 100 chunk_size = 100
chunk_start = 0 chunk_start = 0

View File

@ -1,9 +1,11 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import BooleanField, StringField, SubmitField, SelectField from wtforms import BooleanField, StringField, SubmitField, SelectField
from wtforms.validators import DataRequired, Length
class PJQueryForm(FlaskForm): class PJQueryForm(FlaskForm):
query = StringField('Query') query = StringField('Query',
validators=[DataRequired(), Length(1, 1024)])
submit = SubmitField('Send query') submit = SubmitField('Send query')
@ -33,3 +35,13 @@ class PJDisplayOptionsForm(FlaskForm):
('80', '80'), ('80', '80'),
('90', '90'), ('90', '90'),
('100', '100')]) ('100', '100')])
class QueryDownloadForm(FlaskForm):
file_type = SelectField('File type',
choices=[('', 'Choose file type'),
('csv', 'csv'),
('json', 'json'),
('excel', 'excel'),
('html', 'html-table')],
validators=[DataRequired()])

View File

@ -1,7 +1,7 @@
from flask import request, render_template from flask import request, render_template
from flask_login import login_required from flask_login import login_required
from . import corpora from . import corpora
from .pj_forms import PJDisplayOptionsForm, PJQueryForm from .pj_forms import PJDisplayOptionsForm, PJQueryForm, QueryDownloadForm
from .. import db from .. import db
from ..models import Corpus from ..models import Corpus
@ -19,7 +19,10 @@ def pj_analyse_corpus(corpus_id):
results_per_page=request.args.get('results_per_page', 30)) results_per_page=request.args.get('results_per_page', 30))
query_form = PJQueryForm(prefix='query-form', query_form = PJQueryForm(prefix='query-form',
query=request.args.get('query')) query=request.args.get('query'))
query_download_form = QueryDownloadForm()
return render_template('corpora/pj_analyse_corpus.html.j2', return render_template('corpora/pj_analyse_corpus.html.j2',
corpus_id=corpus_id, corpus_id=corpus_id,
display_options_form=display_options_form, display_options_form=display_options_form,
query_form=query_form, title='Corpus analysis') query_form=query_form,
query_download_form=query_download_form,
title='Corpus analysis')

View File

@ -4,34 +4,70 @@ class CorpusAnalysisClient {
this.corpusId = corpusId; this.corpusId = corpusId;
this.displays = {}; this.displays = {};
this.socket = socket; this.socket = socket;
this.resultList = {};
// js list options and intialization
let displayOptionsData = this.getDisplayOptions(displayOptionsFormElement);
let resultListOptions = {page: displayOptionsData["hitsPerPage"],
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>`};
this.resultList = new ResultList("result-list", resultListOptions);
// socket on event fpr corpous analysis initialization
socket.on("pj_corpus_analysis_init", (response) => { socket.on("pj_corpus_analysis_init", (response) => {
var errorText; var errorText;
if (response.code === 200) { if (response.code === 200) {
console.log(`pj_corpus_analysis_init: ${response.code} - ${response.msg}`); console.log(`pj_corpus_analysis_init: ${response.code} - ${response.msg}`);
if (this.callbacks.init != undefined) {this.callbacks.init(response.payload);} if (this.callbacks.init != undefined) {
if (this.displays.init != undefined) {this.displays.init.setVisibilityByStatus("success");} this.callbacks.init(response.payload);
}
if (this.displays.init != undefined) {
this.displays.init.setVisibilityByStatus("success");
}
} else { } else {
errorText = `Error ${response.code} - ${response.msg}`; errorText = `Error ${response.code} - ${response.msg}`;
if (this.displays.init.errorContainer != undefined) {this.displays.init.errorContainer.innerHTML = `<p class="red-text"><i class="material-icons tiny">error</i> ${errorText}</p>`;} if (this.displays.init.errorContainer != undefined) {
if (this.displays.init != undefined) {this.displays.init.setVisibilityByStatus("error");} this.displays.init.errorContainer.innerHTML = `<p class="red-text"><i class="material-icons tiny">error</i> ${errorText}</p>`;
}
if (this.displays.init != undefined) {
this.displays.init.setVisibilityByStatus("error");
}
console.error(`pj_corpus_analysis_init: ${errorText}`); console.error(`pj_corpus_analysis_init: ${errorText}`);
} }
}); });
// socket on event for recieveing query results
socket.on("pj_corpus_analysis_query", (response) => { socket.on("pj_corpus_analysis_query", (response) => {
var errorText; var errorText;
if (response.code === 200) { if (response.code === 200) {
console.log(`pj_corpus_analysis_query: ${response.code} - ${response.msg}`); console.log(`pj_corpus_analysis_query: ${response.code} - ${response.msg}`);
if (this.callbacks.query != undefined) {this.callbacks.query(response.payload);} if (this.callbacks.query != undefined) {
if (this.displays.query != undefined) {this.displays.query.setVisibilityByStatus("success");} this.callbacks.query(response.payload);
}
if (this.displays.query != undefined) {
this.displays.query.setVisibilityByStatus("success");
}
} else { } else {
errorText = `Error ${response.payload.code} - ${response.payload.msg}`; errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
nopaque.flash("error", errorText); nopaque.flash("error", errorText);
if (this.displays.query.errorContainer != undefined) {this.displays.query.errorContainer.innerHTML = `<p class="red-text"><i class="material-icons tiny">error</i> ${errorText}</p>`;} if (this.displays.query.errorContainer != undefined) {
if (this.displays.query != undefined) {this.displays.query.setVisibilityByStatus("error");} this.displays.query.errorContainer.innerHTML = `<p class="red-text"><i class="material-icons tiny">error</i> ${errorText}</p>`;
}
if (this.displays.query != undefined) {
this.displays.query.setVisibilityByStatus("error");
}
console.error(`pj_corpus_analysis_query: ${errorText}`); console.error(`pj_corpus_analysis_query: ${errorText}`);
} }
}); });
@ -42,14 +78,26 @@ class CorpusAnalysisClient {
} }
init() { init() {
if (this.displays.init.errorContainer != undefined) {this.displays.init.errorContainer.innerHTML == "";} if (this.displays.init.errorContainer != undefined) {
if (this.displays.init != undefined) {this.displays.init.setVisibilityByStatus("waiting");} this.displays.init.errorContainer.innerHTML == "";
}
if (this.displays.init != undefined) {
this.displays.init.setVisibilityByStatus("waiting");
}
this.socket.emit("pj_corpus_analysis_init", this.corpusId); this.socket.emit("pj_corpus_analysis_init", this.corpusId);
} }
query(queryStr) { sendQuery(queryStr) {
if (this.displays.query.errorContainer != undefined) {this.displays.query.errorContainer.innerHTML == "";} let displayOptionsData;
if (this.displays.query != undefined) {this.displays.query.setVisibilityByStatus("waiting");} let resultListOptions;
if (this.displays.query.errorContainer != undefined) {
this.displays.query.errorContainer.innerHTML == "";
}
if (this.displays.query != undefined) {
this.displays.query.setVisibilityByStatus("waiting");
}
this.resultList.clear(); // empty list for new query
nopaque.socket.emit("pj_corpus_analysis_query", queryStr); nopaque.socket.emit("pj_corpus_analysis_query", queryStr);
} }
@ -62,6 +110,18 @@ class CorpusAnalysisClient {
return queryStr return queryStr
} }
getDisplayOptions(displayOptionsFormElement) {
// gets display options parameters
let displayOptionsFormData
let displayOptionsData;
displayOptionsFormData = new FormData(displayOptionsFormElement);
displayOptionsData = {"resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"),
"resultsContex": displayOptionsFormData.get("display-options-form-result_context"),
"expertMode": displayOptionsFormData.get("display-options-form-expert_mode")};
console.log(displayOptionsData);
return displayOptionsData
}
setCallback(type, callback) { setCallback(type, callback) {
// saves callback functions into an object. Key is function type, callback // saves callback functions into an object. Key is function type, callback
// is the callback function // is the callback function
@ -81,30 +141,56 @@ class CorpusAnalysisDisplay {
this.showOnError = element.querySelectorAll(".show-on-error"); this.showOnError = element.querySelectorAll(".show-on-error");
this.showOnSuccess = element.querySelectorAll(".show-on-success"); this.showOnSuccess = element.querySelectorAll(".show-on-success");
this.showWhileWaiting = element.querySelectorAll(".show-while-waiting"); this.showWhileWaiting = element.querySelectorAll(".show-while-waiting");
this.hideOnComplete = element.querySelectorAll(".hide-on-complete")
} }
setVisibilityByStatus(status) { setVisibilityByStatus(status) {
switch (status) { switch (status) {
case "error": case "error":
for (let element of this.showOnError) {element.classList.remove("hide");} for (let element of this.showOnError) {
for (let element of this.showOnSuccess) {element.classList.add("hide");} element.classList.remove("hide");
for (let element of this.showWhileWaiting) {element.classList.add("hide");} }
for (let element of this.showOnSuccess) {
element.classList.add("hide");
}
for (let element of this.showWhileWaiting) {
element.classList.add("hide");
}
break; break;
case "success": case "success":
for (let element of this.showOnError) {element.classList.add("hide");} for (let element of this.showOnError) {
for (let element of this.showOnSuccess) {element.classList.remove("hide");} element.classList.add("hide");
for (let element of this.showWhileWaiting) {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; break;
case "waiting": case "waiting":
for (let element of this.showOnError) {element.classList.add("hide");} for (let element of this.showOnError) {
for (let element of this.showOnSuccess) {element.classList.add("hide");} element.classList.add("hide");
for (let element of this.showWhileWaiting) {element.classList.remove("hide");} }
for (let element of this.showOnSuccess) {
element.classList.add("hide");
}
for (let element of this.showWhileWaiting) {
element.classList.remove("hide");
}
break; break;
default: default:
// Hide all // Hide all
for (let element of this.showOnError) {element.classList.add("hide");} for (let element of this.showOnError) {
for (let element of this.showOnSuccess) {element.classList.add("hide");} element.classList.add("hide");
for (let element of this.showWhileWaiting) {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

@ -101,7 +101,7 @@ function helperSendQuery(queryData) {
outerWindow: 1 outerWindow: 1
}], }],
valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}], valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
item: `<span class="hidden"></span>`}; item: `<span></span>`};
resultList = new ResultList('result-list', resultListOptions); resultList = new ResultList('result-list', resultListOptions);
resultList.clear(); // empty list for new query resultList.clear(); // empty list for new query
} }

View File

@ -1,32 +1,55 @@
function querySetup(payload) { function querySetup(payload) {
// This is called when a query was successfull // This is called when a query was successfull
console.log("Query setup."); // some hiding
console.log(payload); queryResultsExportElement.classList.add("disabled");
console.log("Query initial setup seccessfull.");
queryResultsDeterminateElement.style.width = "0%"; queryResultsDeterminateElement.style.width = "0%";
queryResultsProgressElement.classList.remove("hide"); queryResultsProgressElement.classList.remove("hide");
receivedMatchNumElement.innerText = "0"; queryResultsUserFeedbackElement.classList.remove("hide");
textLookupNumElement.innerText = "0"; receivedMatchCountElement.innerText = "0";
matchNumElement.innerText = payload.num_matches; textLookupCountElement.innerText = "0";
matchCountElement.innerText = payload.match_count;
// always re initializes results to delete old results from it
results = {}; results = {};
results["matches"] = []; // list of all c with lc and rc results["matches"] = []; // list of all c with lc and rc
results["cpos_lookup"] = {}; // object contains all cpos as key value pair results["cpos_lookup"] = {}; // object contains all cpos as key value pair
results["text_lookup"] = {}; // same as above for all text ids results["text_lookup"] = {}; // same as above for all text ids
results["num_matches"] = payload.num_matches; results["match_count"] = payload.match_count;
results["query"] = client.getQueryStr(queryFormElement);
} }
function queryRenderResults(payload) { function queryRenderResults(payload) {
// This is called when results are transmitted. // This is called when results are transmitted and being recieved
console.log("CHUNK:", payload.chunk); console.log("Current recieved chunk:", payload.chunk);
console.log("RESULTS:", results); // upate progress status
if (payload.progress === 100) { if (payload.progress === 100) {
queryResultsProgressElement.classList.add("hide"); queryResultsProgressElement.classList.add("hide");
queryResultsUserFeedbackElement.classList.add("hide");
queryResultsExportElement.classList.remove("disabled");
activateInspect();
} }
// update progress bar
queryResultsDeterminateElement.style.width = `${payload.progress}%`; queryResultsDeterminateElement.style.width = `${payload.progress}%`;
results.matches.push(...payload.chunk.matches); // building the result list js list from incoming chunk
receivedMatchNumElement.innerText = `${results.matches.length}`; resultItems = []; // list for holding every row item
// get infos for full match row
for (let [index, match] of payload.chunk.matches.entries()) {
resultItems.push({...match, ...{"index": index + results.matches.length}});
}
client.resultList.add(resultItems, (items) => {
for (let item of items) {
item.elm = client.resultList.createResultRowElement(item, payload.chunk);
}
client.resultList.update();
changeContext(); // sets lr context on first result load
});
// incorporating new chunk results into full results // incorporating new chunk results into full results
results.matches.push(...payload.chunk.matches);
Object.assign(results.cpos_lookup, payload.chunk.cpos_lookup); Object.assign(results.cpos_lookup, payload.chunk.cpos_lookup);
Object.assign(results.text_lookup, payload.chunk.text_lookup); Object.assign(results.text_lookup, payload.chunk.text_lookup);
// show user current and total match count
textLookupNumElement.innerText = `${Object.keys(results.text_lookup).length}`; receivedMatchCountElement.innerText = `${results.matches.length}`;
textLookupCountElement.innerText = `${Object.keys(results.text_lookup).length}`;
console.log("Results recieved:", results);
} }

View File

@ -149,7 +149,7 @@ class ResultList extends List {
token = chunk["cpos_lookup"][cpos]; token = chunk["cpos_lookup"][cpos];
hitCellElement.insertAdjacentHTML("beforeend", `<span class="token" data-cpos="${cpos}">${token["word"]} </span>`); hitCellElement.insertAdjacentHTML("beforeend", `<span class="token" data-cpos="${cpos}">${token["word"]} </span>`);
// get text titles of every hit cpos token // get text titles of every hit cpos token
textTitles.add(chunk["text_lookup"][token["text"]]["title"]); textTitles.add(chunk["text_lookup"][token["text"]]["text_title"]);
// add button to trigger more context to every match td // add button to trigger more context to every match td
var inspectBtn = document.createElement("a"); var inspectBtn = document.createElement("a");
inspectBtn.setAttribute("class", "btn-floating btn-flat waves-effect waves-light grey right inspect disabled"); inspectBtn.setAttribute("class", "btn-floating btn-flat waves-effect waves-light grey right inspect disabled");

View File

@ -0,0 +1,231 @@
// ###### Download results functions ######
// TODO: Maybe write these as class functions? For this maybe create a result class
// function creates a unique and safe filename for the download
function createDownloadFilename() {
let today;
let currentDate;
let currentTime;
let safeFilename;
let resultFilename;
// get and create metadata
console.log("Create Metadata!");
today = new Date();
currentDate = today.getUTCFullYear() + '-' + (today.getUTCMonth() +1) + '-' + today.getUTCDate();
currentTime = today.getUTCHours() + ":" + today.getUTCMinutes() + ":" + today.getUTCSeconds();
safeFilename = results["query"].replace(/[^a-z0-9_-]/gi, "_");
resultFilename = "UTC-" + currentDate + "_" + currentTime + "_" + safeFilename;
return resultFilename
}
// function to download the results as JSON
function downloadJSONRessource(resultFilename) {
let dataStr;
let downloadElement;
// stringify JSON object for json download
dataStr = JSON.stringify(results, undefined, "\t"); // use tabs to save some space
// get downloadResultsElement
downloadElement = document.getElementById("download-results-json");
// start actual download
download(downloadElement, dataStr, resultFilename, "text/json", ".json")
}
// Function to download data as a Blob created from a string, should be multi purpose
function download(downloadElem, dataStr, filename, type, filenameSlug) {
let file;
console.log("Start Download!");
filename += filenameSlug;
file = new Blob([dataStr], {type: type});
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others
var url = URL.createObjectURL(file);
downloadElem.href = url;
downloadElem.download = filename;
}
}
// ###### Functions to inspect one match, to show more details ######
// activate inspect buttons if queryFinished is true
function activateInspect(progress) {
let inspectBtnElements;
inspectBtnElements = document.getElementsByClassName("inspect");
for (let inspectBtn of inspectBtnElements) {
inspectBtn.classList.remove("disabled");
}
}
//gets result cpos infos for one dataIndex to send back to the server
function inspect(dataIndex) {
console.log("Inspect!");
console.log(result["matches"][dataIndex]["c"]);
contextModal.open();
nopaque.socket.emit("inspect_match", {"cpos": result["matches"][dataIndex]["c"]});
}
function showMatchContext(message) {
let contextResultsElement;
let sentenceElement
let token;
let tokenElement;
console.log("###### match_context ######");
console.log("Incoming data:", message);
contextResultsElement = document.getElementById("context-results");
contextResultsElement.innerHTML = "<p>&nbsp;</p>";
document.getElementById("context-modal-loading").classList.add("hide");
document.getElementById("context-modal-ready").classList.remove("hide");
for (let [key, value] of Object.entries(message['context_s_cpos'])) {
sentenceElement = document.createElement("p");
for (let cpos of value) {
token = message["cpos_lookup"][cpos];
tokenElement = document.createElement("span");
tokenElement.classList.add("token");
if (message["match_cpos_list"].includes(cpos)) {
tokenElement.classList.add("bold");
tokenElement.classList.add("light-green");
}
tokenElement.dataset.cpos = cpos;
tokenElement.innerText = token["word"];
var expertModeSwitchElement = document.getElementById("expert-mode-switch");
if (expertModeSwitchElement.checked) {
expertModeOn([tokenElement], message);
}
sentenceElement.append(tokenElement);
sentenceElement.append(document.createTextNode(" "));
}
contextResultsElement.append(sentenceElement);
}
}
// ###### Display options changing live how the matches are being displayed ######
// Event function that changes the shown hits per page.
// Just alters the resultList.page property
function changeHitsPerPage(event) {
try {
resultList.page = event.target.value;
resultList.update();
nopaque.flash("Updated matches per page.")
} catch (e) {
console.log("resultList has no results right now. Live update of items per page is useless for now.");
}
}
// Event function triggered on context select change and also if pagination is clicked
function changeContext(event) {
let newContextValue;
let lc;
let rc;
let array;
try {
if (event.type === "change") {
nopaque.flash("Updated context per match!");
}
} catch (e) {
// console.log(e);
// console.log("This error is expected.");
} finally {
newContextValue = document.getElementById("display-options-form-result_context").value;
console.log("Context value is:", newContextValue);
lc = document.getElementsByClassName("left-context");
rc = document.getElementsByClassName("right-context");
for (let element of lc) {
array = Array.from(element.childNodes);
for (let element of array.slice(newContextValue)) {
element.classList.add("hide");
}
for (let element of array.slice(0, newContextValue)) {
element.classList.remove("hide");
}
}
for (let element of rc) {
array = Array.from(element.childNodes);
for (let element of array.slice(newContextValue)) {
element.classList.add("hide");
}
for (let element of array.slice(0, newContextValue)) {
element.classList.remove("hide");
}
}
}
}
// ###### Expert view event functions ######
// Event function to check if pagination is used and then look if
// expertModeSwitchElement is checked
// if checked than expertModeOn is executed
// if unchecked expertModeOff is executed
function eventHandlerCheck(event) {
console.log("pagination used!");
console.log(expertModeSwitchElement.checked);
if (expertModeSwitchElement.checked) {
expertModeOn(event.currentTarget.tokenElements, result);
} else if (!expertModeSwitchElement.checked) {
event.preventDefault();
console.log("prevented! Destroy");
expertModeOff(event.currentTarget.tokenElements);
}
}
// function to apply extra information and animation to every token
function expertModeOn(tokenElements, result_lookup) {
let token;
console.log("expertModeOn!");
for (let tokenElement of tokenElements) {
tokenElement.classList.add("chip");
tokenElement.classList.add("hoverable");
tokenElement.classList.add("expert-view");
token = result_lookup["cpos_lookup"][tokenElement.dataset.cpos];
tokenElement.addEventListener("mouseover", function(event) {
console.log("Mouseover!");
console.log(event.target);
token = result_lookup["cpos_lookup"][event.target.dataset.cpos];
addToolTipToTokenElement(event.target, token);
});
}
}
// fuction that creates Tooltip for one token and extracts the corresponding
// infos from the result JSON
function addToolTipToTokenElement(tokenElement, token) {
M.Tooltip.init(tokenElement,
{"html": `<table>
<tr>
<th>Token information</th>
<th>Source information</th>
</tr>
<tr>
<td class="left-align">
Word: ${token["word"]}<br>
Lemma: ${token["lemma"]}<br>
POS: ${token["pos"]}<br>
Simple POS: ${token["simple_pos"]}<br>
NER: ${token["ner"]}
</td>
<td class="left-align">
Title: ${result["text_lookup"][token["text"]]["title"]}<br>
Author: ${result["text_lookup"][token["text"]]["author"]}<br>
Publishing year: ${result["text_lookup"][token["text"]]["publishing_year"]}
</td>
</tr>
</table>`,
"inDuration": 1500,
"margin": 15,
"position": "top",
"transitionMovement": 0});
}
// function to remove extra informations and animations from tokens
function expertModeOff(tokenElements) {
console.log("expertModeOff!");
for (let tokenElement of tokenElements) {
tokenElement.classList.remove("chip");
tokenElement.classList.remove("hoverable");
tokenElement.classList.remove("expert-view");
tokenElement.outerHTML = tokenElement.outerHTML; // this is actually a workaround, but it works pretty fast
}
}

View File

@ -78,20 +78,58 @@
</ul> </ul>
</div> </div>
<!-- entire results div/card -->
<div class="col s12" id="query-display"> <div class="col s12" id="query-display">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content" id="result-list" style="overflow: hidden;">
<span class="card-title">Query Results</span> <span class="card-title">Query Results</span>
<div class="error-container hide show-on-error"></div> <div class="error-container hide show-on-error"></div>
<div class="hide show-on-success"> <div class="hide show-on-success">
<p> <div class="col s12 m6 l6">
<span id="received-match-num"></span> of <span id="match-num"></span> matches loaded.<br> <div class="row">
Matches occured in <span id="text-lookup-num"></span> corpus files. <p>
</p> <span id="received-match-count">
<div class="progress" id="query-results-progress"> </span> of
<div class="determinate" id="query-results-determinate"></div> <span id="match-count"></span>
matches loaded.
<br>
Matches occured in <span id="text-lookup-count"></span> corpus files.
</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>
<div class="row">
<div class="progress" id="query-results-progress">
<div class="determinate" id="query-results-determinate"></div>
</div>
</div>
</div>
<div class="col s12 m6 l6">
<div class="row">
<button id="query-results-export" class="waves-effect waves-light btn-small right disabled" type="submit">Export Results<i class="material-icons right">file_download</i></button>
</div>
</div>
<!-- Table showing the query results -->
<div class="col s12">
<ul class="pagination paginationTop"></ul>
<table class="responsive-table highlight">
<thead>
<tr>
<th style="width: 2%">Nr.</th>
<th style="width: 3%">Title</th>
<th style="width: 25%">Left context</th>
<th style="width: 45%">Match</th>
<th style="width: 25%">Right Context</th>
</tr>
</thead>
<tbody class="list" id="query-results">
</tbody>
</table>
<ul class="pagination paginationBottom"></ul>
</div> </div>
<div id="query-results"></div>
</div> </div>
</div> </div>
</div> </div>
@ -99,6 +137,7 @@
<!-- Modals --> <!-- Modals -->
<!-- Analysis init modal -->
<div class="modal no-autoinit" id="init-display"> <div class="modal no-autoinit" id="init-display">
<div class="modal-content"> <div class="modal-content">
<h4>Initializing your corpus analysis session...</h4> <h4>Initializing your corpus analysis session...</h4>
@ -109,11 +148,60 @@
</div> </div>
</div> </div>
<!-- Export query results modal -->
<div id="query-results-download-modal"
class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
{{ query_download_form.hidden_tag() }}
<h4>Download current query Results</h4>
<p>The results of the current query can be downlaoded as several files like csv or json. Those files can be used in other software like excel. Also it is easy to publish your results as raw data like this!</p>
<table>
<tr>
<td>JSON</td>
<td>
<a class="btn waves-effect waves-light" id="download-results-json">Download
<i class="material-icons right">file_download</i>
</a>
</td>
</tr>
<tr>
<td>CSV</td>
<td>
<a class="btn waves-effect waves-light disabled" id="download-results-csv">Download
<i class="material-icons right">file_download</i>
</a>
</td>
</tr>
<tr>
<td>EXCEL</td>
<td>
<a class="btn waves-effect waves-light disabled">Download
<i class="material-icons right">file_download</i>
</a>
</td>
</tr>
<tr>
<td>HTML</td>
<td>
<a class="btn waves-effect waves-light disabled">Download
<i class="material-icons right">file_download</i>
</a>
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-light red btn">Close</a>
</div>
</div>
<script src="{{ url_for('static', filename='js/nopaque.CorpusAnalysisClient.js') }}"> <script src="{{ url_for('static', filename='js/nopaque.CorpusAnalysisClient.js') }}">
</script> </script>
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}"> <script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
</script> </script>
<script src="{{ url_for('static', filename='js/nopaque.pj_analyse_corpus.js') }}">
</script>
<script> <script>
// ###### Defining global variables used in other functions ###### // ###### Defining global variables used in other functions ######
var results; // full JSON object holding match results var results; // full JSON object holding match results
@ -121,32 +209,44 @@
var collapsibleElements; // all collapsibles on site var collapsibleElements; // all collapsibles on site
var queryResultsProgressElement; // Div element holding the progress bar var queryResultsProgressElement; // Div element holding the progress bar
var queryResultsDeterminateElement; // The progress bar for recieved results var queryResultsDeterminateElement; // The progress bar for recieved results
var queryResultsUserFeedbackElement; // Element showing match count|total etc
var queryResultsExportElement; // Download button opens download modal
var exportModal;
var initModal;
var downloadResultsJSONElement; // button for downloading results as JSON
var displayOptionsFormElement;
// ###### Initialize variables ###### // ###### Initialize variables ######
queryFormElement = document.getElementById("query-form");
queryResultsProgressElement = document.getElementById("query-results-progress");
queryResultsDeterminateElement = document.getElementById("query-results-determinate");
queryResultsUserFeedbackElement = document.getElementById("query-results-user-feedback");
queryResultsExportElement = document.getElementById("query-results-export");
displayOptionsFormElement = document.getElementById("display-options-form");
// ###### Set some css options ######
// get collapsibles to alter options of those // get collapsibles to alter options of those
var collapsibleElements = document.querySelector('.collapsible.expandable'); var collapsibleElements = document.querySelector('.collapsible.expandable');
var client = undefined; var client = undefined;
var initDisplay = undefined; var initDisplay = undefined;
var queryDisplay = undefined; var queryDisplay = undefined;
var initDisplayElement = document.getElementById("init-display"); var initDisplayElement = document.getElementById("init-display");
var queryDisplayElement = document.getElementById("query-display"); var queryDisplayElement = document.getElementById("query-display");
var queryFormElement = document.getElementById("query-form"); receivedMatchCountElement = document.getElementById("received-match-count");
var queryResultsDeterminateElement = document.getElementById("query-results-determinate"); matchCountElement = document.getElementById("match-count");
var queryResultsProgressElement = document.getElementById("query-results-progress"); textLookupCountElement = document.getElementById("text-lookup-count");
receivedMatchNumElement = document.getElementById("received-match-num");
matchNumElement = document.getElementById("match-num");
textLookupNumElement = document.getElementById("text-lookup-num");
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
//set accordion of collapsibles to false //set accordion of collapsibles to false
M.Collapsible.init(collapsibleElements, {accordion: false}); M.Collapsible.init(collapsibleElements, {accordion: false});
// creates some models on DOMContentLoaded
var initModal = M.Modal.init(initDisplayElement, {dismissible: false}); exportModal = M.Modal.init(document.getElementById("query-results-download-modal"),
{"dismissible": true});
initModal = M.Modal.init(initDisplayElement, {dismissible: false});
// Init corpus analysis components // Init corpus analysis components
initDisplay = new CorpusAnalysisDisplay(initDisplayElement); initDisplay = new CorpusAnalysisDisplay(initDisplayElement);
queryDisplay = new CorpusAnalysisDisplay(queryDisplayElement); queryDisplay = new CorpusAnalysisDisplay(queryDisplayElement);
@ -157,6 +257,7 @@
client.setCallback("init", () => { client.setCallback("init", () => {
initModal.close(); initModal.close();
}); });
client.setDisplay("query", queryResultsUserFeedbackElement);
client.setDisplay("query", queryDisplay); client.setDisplay("query", queryDisplay);
client.setCallback("query", (payload) => { client.setCallback("query", (payload) => {
querySetup(payload); querySetup(payload);
@ -173,8 +274,19 @@
event.preventDefault(); event.preventDefault();
// Get query string and send query to server // Get query string and send query to server
let queryStr = client.getQueryStr(queryFormElement); let queryStr = client.getQueryStr(queryFormElement);
client.query(queryStr); client.sendQuery(queryStr);
}); });
}); });
// Add onclick to open download modal wen Export Results button is pressed
queryResultsExportElement.onclick = function() {
exportModal.open();
}
// add onclick to download JSON button and download the file
downloadResultsJSONElement = document.getElementById("download-results-json")
downloadResultsJSONElement.onclick = function() {
let filename = createDownloadFilename();
downloadJSONRessource(filename)};
</script> </script>
{% endblock %} {% endblock %}