From 9031ca5845c8f48a34798e2f90e6cd6f7e0c65b5 Mon Sep 17 00:00:00 2001
From: Stephan Porada <sporada@uni-bielefeld.de>
Date: Wed, 15 Apr 2020 14:55:29 +0200
Subject: [PATCH] New context options

---
 app/corpora/events.py                        |   2 +-
 app/corpora/forms.py                         |  18 +---
 app/static/js/nopaque.lists.js               | 105 ++++++++++++++-----
 app/templates/corpora/analyse_corpus.html.j2 |  84 ++++++++-------
 4 files changed, 131 insertions(+), 78 deletions(-)

diff --git a/app/corpora/events.py b/app/corpora/events.py
index bec50125..1fc3fb5d 100644
--- a/app/corpora/events.py
+++ b/app/corpora/events.py
@@ -93,7 +93,7 @@ def corpus_analysis_inspect_match(payload):
     try:
         corpus = client.corpora.get('CORPUS')
         s = corpus.structural_attributes.get('s')
-        payload = s.export(payload['first_cpos'], payload['last_cpos'], context=3)
+        payload = s.export(payload['first_cpos'], payload['last_cpos'], context=10)
         payload['cpos_ranges'] = True
     except cqi.errors.CQiException as e:
         payload = {'code': e.code, 'desc': e.description, 'msg': e.name}
diff --git a/app/corpora/forms.py b/app/corpora/forms.py
index 82745e76..99f6b733 100644
--- a/app/corpora/forms.py
+++ b/app/corpora/forms.py
@@ -1,7 +1,7 @@
 from flask_wtf import FlaskForm
 from wtforms import (BooleanField, FileField, StringField, SubmitField,
                      ValidationError, IntegerField, SelectField)
-from wtforms.validators import DataRequired, Length
+from wtforms.validators import DataRequired, Length, NumberRange
 
 
 class AddCorpusFileForm(FlaskForm):
@@ -88,19 +88,9 @@ class DisplayOptionsForm(FlaskForm):
 class InspectDisplayOptionsForm(FlaskForm):
     expert_mode_inspect = BooleanField('Expert mode')
     highlight_sentences = BooleanField('Highlight sentences')
-    context_sentences = SelectField('Context sentences',
-                                    choices=[('', 'Choose your option'),
-                                             ('1', '1'),
-                                             ('2', '2'),
-                                             ('3', '3'),
-                                             ('4', '4'),
-                                             ('5', '5'),
-                                             ('6', '6'),
-                                             ('7', '7'),
-                                             ('8', '8'),
-                                             ('9', '9'),
-                                             ('10', '10')],
-                                    default=3)
+    context_sentences = IntegerField('Context sentences',
+                                     validators=[NumberRange(min=0, max=10)],
+                                     default=3)
 
 
 class QueryDownloadForm(FlaskForm):
diff --git a/app/static/js/nopaque.lists.js b/app/static/js/nopaque.lists.js
index 8cfda3c9..009a5bf9 100644
--- a/app/static/js/nopaque.lists.js
+++ b/app/static/js/nopaque.lists.js
@@ -177,62 +177,111 @@ class ResultsList extends List {
           );
   }
 
+  HTMLTStroElement(htmlStr) {
+    // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
+    let template = document.createElement("template");
+    htmlStr = htmlStr.trim();
+    template.innerHTML = htmlStr;
+    return template.content.firstChild;
+    }
+
   showMatchContext(response) {
+    this.contextData;
     let c;
-    let contextData;
     let contextModalLoading;
     let contextModalReady;
     let contextResultsElement;
     let expertModeSwitchElement;
     let lc;
+    let modalTokenElements;
     let partElement;
     let rc;
     let token;
-    let tokenElement;
-    let tokenElements;
-    contextData = response.payload;
+    let tokenHTMLArray;
+    let uniqueS;
+    let htmlTokenStr;
+    let tokenHTMlElement;
+
+    this.contextData = response.payload;
     contextResultsElement = document.getElementById("context-results");
     expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
+    uniqueS = new Set();
     // check if cpos ranges are used or not
-    if (contextData.cpos_ranges == true) {
+    if (this.contextData.cpos_ranges == true) {
       // python range like function from MDN
       // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range)
       const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
-      lc = range(contextData.match.lc[0], contextData.match.lc[1], 1)
-      c = range(contextData.match.c[0], contextData.match.c[1], 1)
-      rc = range(contextData.match.rc[0], contextData.match.rc[1], 1)
+      lc = range(this.contextData.match.lc[0], this.contextData.match.lc[1], 1)
+      c = range(this.contextData.match.c[0], this.contextData.match.c[1], 1)
+      rc = range(this.contextData.match.rc[0], this.contextData.match.rc[1], 1)
     } else {
-      lc = contextData.match.lc;
-      c = contextData.match.c;
-      rc = contextData.match.rc;
+      lc = this.contextData.match.lc;
+      c = this.contextData.match.c;
+      rc = this.contextData.match.rc;
     }
     // create sentence strings as tokens
-    partElement = document.createElement("p");
+    tokenHTMLArray = [];
     for (let cpos of lc) {
-      token = contextData.cpos_lookup[cpos];
-      partElement.insertAdjacentHTML("beforeend",
-        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
-      contextResultsElement.append(partElement);
+      token = this.contextData.cpos_lookup[cpos];
+      uniqueS.add(token.s)
+      htmlTokenStr = `<span class="token"` +
+                           `data-sid="${token.s}"` +
+                           `data-cpos="${cpos}">` +
+                       `${token.word}` +
+                     `</span>`;
+      tokenHTMlElement = this.HTMLTStroElement(htmlTokenStr)
+      tokenHTMLArray.push(tokenHTMlElement);
     }
     for (let cpos of c) {
-      token = contextData.cpos_lookup[cpos];
-      partElement.insertAdjacentHTML("beforeend",
-        `<span class="token bold light-green" data-cpos="${cpos}"` +
-        `style="text-decoration-line: underline;">${token.word} </span>`);
-      contextResultsElement.append(partElement);
+      token = this.contextData.cpos_lookup[cpos];
+      uniqueS.add(token.s)
+      htmlTokenStr = `<span class="token bold light-green"` +
+                           `data-sid="${token.s}"` +
+                           `data-cpos="${cpos}"` +
+                           `style="text-decoration-line: underline;">` +
+                       `${token.word}` +
+                     `</span>`;
+      tokenHTMlElement = this.HTMLTStroElement(htmlTokenStr)
+      tokenHTMLArray.push(tokenHTMlElement);
     }
     for (let cpos of rc) {
-      token = contextData.cpos_lookup[cpos];
-      partElement.insertAdjacentHTML("beforeend",
-        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
-      contextResultsElement.append(partElement);
+      token = this.contextData.cpos_lookup[cpos];
+      uniqueS.add(token.s)
+      htmlTokenStr = `<span class="token"` +
+                           `data-sid="${token.s}"` +
+                           `data-cpos="${cpos}">` +
+                       `${token.word}` +
+                     `</span>`;
+      tokenHTMlElement = this.HTMLTStroElement(htmlTokenStr)
+      tokenHTMLArray.push(tokenHTMlElement);
+    }
+    console.log(tokenHTMLArray);
+    console.log(uniqueS);
+
+    partElement = document.createElement("p");
+    for (let sId of uniqueS) {
+      let htmlSentence = `<div class="sentence s12" data-sid="${sId}">`;
+      let sentenceElement = this.HTMLTStroElement(htmlSentence);
+      for (let tokenElement of tokenHTMLArray) {
+        if (tokenElement.dataset.sid == sId) {
+          sentenceElement.appendChild(tokenElement);
+          sentenceElement.insertAdjacentHTML("beforeend", `<span>&nbsp;</span>`);
+        } else {
+          continue;
+        }
+      }
+      partElement.appendChild(sentenceElement);
     }
+    contextResultsElement.appendChild(partElement);
+
     if (expertModeSwitchElement.checked) {
-      tokenElements = partElement.getElementsByClassName("token");
       this.expertModeOn();
     }
   }
 
+  changeSentenceContext(sValue) {
+  }
+
   // ###### Display options changing live how the matches are being displayed ######
 
   // Event function that changes the shown hits per page.
@@ -309,6 +358,9 @@ class ResultsList extends List {
     // console.log("Create Tooltip on mouseover.");
     let token;
     token = results.resultsJSON.cpos_lookup[event.target.dataset.cpos];
+    if (!token) {
+      token = this.contextData.cpos_lookup[event.target.dataset.cpos];
+    }
     this.addToolTipToTokenElement(event.target, token);
   }
 
@@ -321,7 +373,6 @@ class ResultsList extends List {
 
   expertModeOn() {
     // console.log("Expert mode is on.");
-    let token;
     this.currentExpertTokenElements = document.getElementsByClassName("token");
     this.tooltipEventCreateBind = this.tooltipEventCreate.bind(this);
     this.tooltipEventDestroyBind = this.tooltipEventDestroy.bind(this);
diff --git a/app/templates/corpora/analyse_corpus.html.j2 b/app/templates/corpora/analyse_corpus.html.j2
index bbd9fa31..02f30024 100644
--- a/app/templates/corpora/analyse_corpus.html.j2
+++ b/app/templates/corpora/analyse_corpus.html.j2
@@ -201,38 +201,46 @@
     <h4>Match context</h4>
     <div class="divider"></div>
     <div class="section" id="inspect-display-options">
-      <h5>Display options</h5>
-      <div class="row">
-        <div class="col s9">
-          <p>Expert Mode</p>
-        </div>
-        <div class="col s3 right-align">
-          <div class="switch">
-            <label>
-              {{ inspect_display_options_form.expert_mode_inspect() }}
-              <span class="lever"></span>
-            </label>
+      <form>
+        <h5>Display options</h5>
+        <div class="row">
+          <div class="col s9">
+            <p>{{ inspect_display_options_form.expert_mode_inspect.label.text }}</p>
           </div>
-        </div>
-        <div class="col s9">
-          <p>Highlight sentences</p>
-        </div>
-        <div class="col s3 right-align">
-          <div class="switch">
-            <label>
-              {{ inspect_display_options_form.highlight_sentences() }}
-              <span class="lever"></span>
-            </label>
+          <div class="col s3 right-align">
+            <div class="switch">
+              <label>
+                {{ inspect_display_options_form.expert_mode_inspect() }}
+                <span class="lever"></span>
+              </label>
+            </div>
           </div>
-        </div>
-        <div class="col s12 m6">
-          <div class="input-field">
-            <i class="material-icons prefix">format_list_numbered</i>
-            {{ inspect_display_options_form.context_sentences() }}
-            {{ inspect_display_options_form.context_sentences.label }}
+          <div class="col s9">
+            <p>{{ inspect_display_options_form.highlight_sentences.label.text }}</p>
+          </div>
+          <div class="col s3 right-align">
+            <div class="switch">
+              <label>
+                {{ inspect_display_options_form.highlight_sentences() }}
+                <span class="lever"></span>
+              </label>
+            </div>
+          </div>
+          <div class="col s6">
+            <p>Nr. of sentences around the match</p>
+          </div>
+          <div class="col s6 right-align">
+            <div class="input-field">
+              <p class="range-field">
+                <input type="range" id="context-sentences"
+                       min="1"
+                       max="10"
+                       value="3" />
+              </p>
+            </div>
           </div>
         </div>
-      </div>
+      </form>
     </div>
     <div class="divider"></div>
     <div class="section" id="context-results">
@@ -257,16 +265,20 @@
   var collapsibleElements;  // All collapsibleElements on this page
   var collapsibleElements;  // all collapsibles on site
   var contextModal;  // Modal to open on inspect for further match context
+  var contextPerItemElement;
+  var contextSentencesElement;
   var displayOptionsData;  // Getting form data from display options
   var displayOptionsFormElement;  // Form holding the display informations
   var downloadResultsJSONElement;  // button for downloading results as JSON
   var expertModeSwitchElement; // Expert mode switch Element
   var exportModal;  // Download options modal
   var firstPageElement;  // first page element of resultsList pagination
+  var hitsPerPageInputElement;
   var initDisplay;  // CorpusAnalysisDisplay object first undfined on DOMContentLoaded defined
   var initDisplayElement;  // Element for initialization using initDisplay
   var initModal;
   var matchCountElement;  // Total nr. of matches will be displayed in this element
+  var paginationElements;
   var progress;  // global progress value
   var queryDisplay; // CorpusAnalysisDisplay object first undfined on DOMContentLoaded defined
   var queryDisplayElement;  // Element for initialization using queryDisplay
@@ -282,20 +294,19 @@
   var resultsListOptions;  // specifies ResultsList options
   var textLookupCountElement  // Nr of texts the matches occured in will be shown in this element
   var xpath;  // xpath to grab first resultsList page pagination element
-  var hitsPerPageInputElement;
-  var paginationElements;
-  var contextPerItemElement;
 
   // ###### Initialize variables ######
   client = undefined;
   collapsibleElements = document.querySelector('.collapsible.expandable');
   contextModal = document.getElementById("context-modal");
+  contextSentencesElement = document.getElementById("context-sentences");
   displayOptionsFormElement = document.getElementById("display-options-form");
   expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
   exportModal = document.getElementById("query-results-download-modal");
   initDisplay = undefined;
   initDisplayElement = document.getElementById("init-display");
   matchCountElement = document.getElementById("match-count");
+  paginationElements = document.getElementsByClassName("pagination");
   queryDisplay = undefined;
   queryDisplayElement = document.getElementById("query-display");
   queryFormElement = document.getElementById("query-form");
@@ -305,7 +316,6 @@
   queryResultsUserFeedbackElement = document.getElementById("query-results-user-feedback");
   receivedMatchCountElement = document.getElementById("received-match-count");
   textLookupCountElement = document.getElementById("text-lookup-count");
-  paginationElements = document.getElementsByClassName("pagination");
 
   // ###### js list options and intialization ######
   displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
@@ -388,6 +398,12 @@
     contextPerItemElement = document.getElementById("display-options-form-result_context");
     contextPerItemElement.onchange = results.resultsList.changeContext;
 
+    // live update of context sentences in inspect modal
+    contextSentencesElement.onchange = (event) => {
+      let sValue = event.target.value;
+      console.log(sValue);
+    }
+
     // eventListener if pagination is used to apply new context size to new page
     // and also activate inspect match if progress is 100
     for (let element of paginationElements) {
@@ -395,10 +411,6 @@
       element.addEventListener("click", results.resultsList.activateInspect);
     }
 
-    // epxert mode table view
-    // TODO: Redo this
-    // - This replicates itself on expertModeSwitchElement use
-    // - Replication should be fixed
     expertModeSwitchElement.addEventListener("change", (event) => {
       if (event.target.checked) {
         results.resultsList.expertModeOn();
-- 
GitLab