From fe7f69d5965090f9a5540b2dcee95bfb46c4aa15 Mon Sep 17 00:00:00 2001
From: Inga Kirschnick <inga.kirschnick@uni-bielefeld.de>
Date: Mon, 21 Aug 2023 07:26:54 +0200
Subject: [PATCH] QB form update + incidence modifier

---
 app/static/css/queryBuilder.css               |  45 +++-----
 .../CorpusAnalysisConcordance.js              |  31 +++--
 app/static/js/CorpusAnalysis/QueryBuilder.js  |  45 --------
 .../ElementReferencesQueryBuilder.js          |   8 +-
 .../GeneralFunctionsQueryBuilder.js           |  68 ++++++-----
 ...alAttributeBuilderFunctionsQueryBuilder.js |  22 ++++
 ...enAttributeBuilderFunctionsQueryBuilder.js |  37 +++++-
 .../query_builder/_expert_mode.html.j2        |   2 +-
 .../query_builder/_query_builder.html.j2      | 107 +++++++++++-------
 9 files changed, 197 insertions(+), 168 deletions(-)

diff --git a/app/static/css/queryBuilder.css b/app/static/css/queryBuilder.css
index 6cc4a5a1..08bbb778 100644
--- a/app/static/css/queryBuilder.css
+++ b/app/static/css/queryBuilder.css
@@ -1,3 +1,12 @@
+#corpus-analysis-concordance-query-builder-input-field-container {
+  border-bottom: #9E9E9E 1px solid;
+  height: 60px;
+}
+
+#corpus-analysis-concordance-query-builder-input-field {
+  margin-top: 23px;
+}
+
 .modal-content {
   overflow-x: hidden;
 }
@@ -79,40 +88,20 @@
   margin-right: 10px;
 }
 
-#corpus-analysis-concordance-ignore-case-checkbox {
-  margin-left: 5px;
-}
-
-#corpus-analysis-concordance-incidence-modifiers-dropdown a{
-  background-color: white;
-}
-
-[data-target="corpus-analysis-concordance-incidence-modifiers-dropdown"] {
-  background-color: #2FBBAB;
-}
-
-#corpus-analysis-concordance-or, #corpus-analysis-concordance-and {
-  background-color: #fc0;
-}
-
-#corpus-analysis-concordance-betweenNM {
-  width: 60%;
-}
-
-#corpus-analysis-concordance-query-builder-tutorial-modal {
-  width: 60%;
+[data-target="corpus-analysis-concordance-character-incidence-modifiers-dropdown"], [data-target="corpus-analysis-concordance-token-incidence-modifiers-dropdown"] {
+  background-color: #2FBBAB !important;
 }
 
-#corpus-analysis-concordance-query-builder-tutorial-modal ul {
-  margin-top: 10px;
+#corpus-analysis-concordance-exactly-n-token-modal, #corpus-analysis-concordance-between-nm-token-modal {
+  width: 30%;
 }
 
-#corpus-analysis-concordance-query-builder-tutorial {
-  padding:15px;
+[data-modal-id="corpus-analysis-concordance-exactly-n-token-modal"], [data-modal-id="corpus-analysis-concordance-between-nm-token-modal"] {
+  margin-top: 15px !important;
 }
 
-#corpus-analysis-concordance-scroll-up-button-query-builder-tutorial {
-  background-color: #28B3D1;
+[data-options-action="and"], [data-options-action="or"] {
+  background-color: #fc0 !important;
 }
 
 [data-type="start-sentence"], [data-type="end-sentence"] {
diff --git a/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js b/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js
index 891e12f7..91ce0f68 100644
--- a/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js
+++ b/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js
@@ -10,7 +10,8 @@ class CorpusAnalysisConcordance {
       container: document.querySelector(`#corpus-analysis-concordance-container`),
       error: document.querySelector(`#corpus-analysis-concordance-error`),
       userInterfaceForm: document.querySelector(`#corpus-analysis-concordance-user-interface-form`),
-      form: document.querySelector(`#corpus-analysis-concordance-form`),
+      expertModeForm: document.querySelector(`#corpus-analysis-concordance-expert-mode-form`),
+      queryBuilderForm: document.querySelector(`#corpus-analysis-concordance-query-builder-form`),
       progress: document.querySelector(`#corpus-analysis-concordance-progress`),
       subcorpusInfo: document.querySelector(`#corpus-analysis-concordance-subcorpus-info`),
       subcorpusActions: document.querySelector(`#corpus-analysis-concordance-subcorpus-actions`),
@@ -30,12 +31,14 @@ class CorpusAnalysisConcordance {
     this.app.registerExtension(this);
   }
 
-  async submitForm() {
+  async submitForm(queryModeId) {
     this.app.disableActionElements();
-    // let query = this.elements.form.query.value.trim();
-    let query = this.checkQueryInput();
-    console.log(query);
-    let subcorpusName = this.elements.form['subcorpus-name'].value;
+    let queryBuilderQuery = document.querySelector('#corpus-analysis-concordance-query-preview').innerHTML.trim();
+    let expertModeQuery = this.elements.expertModeForm.query.value.trim();
+    let query = queryModeId === 'corpus-analysis-concordance-expert-mode-form' ? expertModeQuery : queryBuilderQuery;
+    let form = queryModeId === 'corpus-analysis-concordance-expert-mode-form' ? this.elements.expertModeForm : this.elements.queryBuilderForm;
+
+    let subcorpusName = form['subcorpus-name'].value;
     this.elements.error.innerText = '';
     this.elements.error.classList.add('hide');
     this.elements.progress.classList.remove('hide');
@@ -74,9 +77,13 @@ class CorpusAnalysisConcordance {
     this.data.corpus = this.app.data.corpus;
     this.data.subcorpora = {};
     // Add event listeners
-    this.elements.form.addEventListener('submit', (event) => {
+    this.elements.expertModeForm.addEventListener('submit', (event) => {
+      event.preventDefault();
+      this.submitForm(this.elements.expertModeForm.id);
+    });
+    this.elements.queryBuilderForm.addEventListener('submit', (event) => {
       event.preventDefault();
-      this.submitForm();
+      this.submitForm(this.elements.queryBuilderForm.id);
     });
     this.elements.userInterfaceForm.addEventListener('change', (event) => {
       if (event.target === this.elements.userInterfaceForm['context']) {
@@ -98,14 +105,6 @@ class CorpusAnalysisConcordance {
     });
   }
 
-  checkQueryInput() {
-    if (document.querySelector('#corpus-analysis-concordance-expert-mode-display').classList.contains('hide')) {
-      return document.querySelector('#corpus-analysis-concordance-query-preview').innerHTML.trim();
-    } else {
-      return this.elements.form.query.value.trim();
-    }
-  }
-
   clearSubcorpusList() {
     this.elements.subcorpusList.innerHTML = '';
     this.elements.subcorpusList.classList.add('hide');
diff --git a/app/static/js/CorpusAnalysis/QueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder.js
index 00388622..ad097048 100644
--- a/app/static/js/CorpusAnalysis/QueryBuilder.js
+++ b/app/static/js/CorpusAnalysis/QueryBuilder.js
@@ -7,51 +7,6 @@ class ConcordanceQueryBuilder {
     this.tokenAttributeBuilderFunctions = new TokenAttributeBuilderFunctionsQueryBuilder(this.elements);
     this.structuralAttributeBuilderFunctions = new StructuralAttributeBuilderFunctionsQueryBuilder(this.elements);
 
-    // Event listener for structural attribute modal
-    document.querySelectorAll('[data-structural-attr-modal-action-button]').forEach(button => {
-      button.addEventListener('click', () => {
-        this.structuralAttributeBuilderFunctions.actionButtonInStrucAttrModalHandler(button.dataset.structuralAttrModalActionButton);
-      });
-    });
-
-    // Event listener for token attribute modal
-    this.elements.positionalAttrSelection.addEventListener('change', (event) => {
-      this.generalFunctions.toggleClass(['word', 'lemma', 'english-pos', 'german-pos', 'simple-pos'], 'hide', 'add');
-      if (event.target.value !== 'empty-token') {
-        this.generalFunctions.toggleClass([event.target.value], 'hide', 'remove');
-        this.generalFunctions.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
-        this.tokenAttributeBuilderFunctions.resetMaterializeSelection([this.elements.englishPosSelection, this.elements.germanPosSelection, this.elements.simplePosSelection]);
-      }
-      if (event.target.value === 'word' || event.target.value === 'lemma') {
-        this.generalFunctions.toggleClass(['input-field-options'], 'hide', 'remove');
-      } else if (event.target.value === 'empty-token'){
-        this.tokenAttributeBuilderFunctions.addTokenToQuery();
-      } else {
-        this.generalFunctions.toggleClass(['input-field-options'], 'hide', 'add');
-      }
-    });
-    // Options for positional attribute selection
-    document.querySelectorAll('.positional-attr-options-action-button[data-options-action]').forEach(button => {
-      button.addEventListener('click', () => {this.tokenAttributeBuilderFunctions.actionButtonInOptionSectionHandler(button.dataset.optionsAction);});
-    });
-    document.querySelectorAll('.incidence-modifier-selection[data-incidence-modifier]').forEach(button => {
-      button.addEventListener('click', () => {this.tokenAttributeBuilderFunctions.incidenceModifierHandler(button);});
-    });
-    document.querySelectorAll('.n-m-submit-button').forEach(button => {
-      button.addEventListener('click', () => {
-        this.tokenAttributeBuilderFunctions.nmSubmitHandler(button.dataset.modalId);
-      });
-    });
-
-    // Initializing and styling the Materialize Chip components
-    M.Chips.init(
-      this.elements.queryInputField, 
-      {
-        placeholder: 'Add your query here'
-      }
-    );
-    document.querySelector('#corpus-analysis-concordance-form-query-builder input').style.setProperty('width', '150px', 'important');
-
     this.elements.positionalAttrModal = M.Modal.init(
       document.querySelector('#corpus-analysis-concordance-positional-attr-modal'), 
       {
diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js
index f75c3a99..c6dcd257 100644
--- a/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js
+++ b/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js
@@ -1,13 +1,11 @@
 class ElementReferencesQueryBuilder {
   constructor() {
     // General Elements
-    this.counter = 0;
-    this.queryInputField = document.querySelector('#corpus-analysis-concordance-form-query-builder');
-    this.queryInputFieldInstance = M.Chips.getInstance(this.queryInputField);
-    this.queryInputFieldContent = [];
+    this.queryInputField = document.querySelector('#corpus-analysis-concordance-query-builder-input-field');
+    this.queryChipElements = [];
 
     // Structural Attribute Builder Elements
-    this.structuralAttrModalInstance = document.querySelector('#corpus-analysis-concordance-structural-attr-modal');
+    this.structuralAttrModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-structural-attr-modal'));
     this.sentenceElement = document.querySelector('[data-structural-attr-modal-action-button="sentence"]');
     this.entityElement = document.querySelector('[data-structural-attr-modal-action-button="entity"]');
     this.textAnnotationElement = document.querySelector('[data-structural-attr-modal-action-button="text-annotation"]');
diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js
index 0208442e..70b6115d 100644
--- a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js
+++ b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js
@@ -9,23 +9,29 @@ class GeneralFunctionsQueryBuilder {
     });
   }
 
-  queryChipFactory(dataType, prettyQueryText, queryText) {
-    this.elements.counter++;
-    this.elements.queryInputFieldInstance.addChip({
-      tag: prettyQueryText
-    });
+  updateChipList() {
+    this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.chip');
+  }
 
-    let queryChipElementIndex = this.elements.queryInputFieldInstance.$chips.length - 1;
-    let queryChipElement = this.elements.queryInputFieldInstance.$chips[queryChipElementIndex];
-    queryChipElement.classList.add('query-component');
-    queryChipElement.setAttribute('data-type', dataType);
-    queryChipElement.setAttribute('data-query', queryText);
-    queryChipElement.setAttribute('draggable', 'true');
+  queryChipFactory(dataType, prettyQueryText, queryText) {
+    queryText = Utils.escape(queryText);
+    prettyQueryText = Utils.escape(prettyQueryText);
+    let queryChipElement = Utils.HTMLToElement(
+      `
+        <span class="chip query-component" data-type="${dataType}" data-query="${queryText}" draggable="true">
+          ${prettyQueryText}
+          <i class="material-icons close">close</i>
+        </span>
+      `
+    );
 
-    queryChipElement.addEventListener('click', () => this.deleteAttr(queryChipElement));
+    queryChipElement.addEventListener('click', () => this.selectChipElement(queryChipElement));
+    queryChipElement.querySelector('i').addEventListener('click', () => this.deleteChipElement(queryChipElement));
+    
     queryChipElement.addEventListener('dragstart', this.handleDragStart.bind(this, queryChipElement));
     queryChipElement.addEventListener('dragend', this.handleDragEnd);
-
+    this.elements.queryInputField.appendChild(queryChipElement);
+    this.updateChipList();
     this.queryPreviewBuilder();
   }
 
@@ -65,45 +71,51 @@ class GeneralFunctionsQueryBuilder {
     targetChipClone.addEventListener('drop', (event) => {
       let dropzone = event.target;
       dropzone.parentElement.replaceChild(queryChipElement, dropzone);
-      this.elements.queryInputFieldInstance.$chips = Array.from(this.elements.queryInputField.querySelectorAll('.chip'));
+      this.updateChipList();
       this.queryPreviewBuilder();
     });
   }
 
   queryPreviewBuilder() {
     let queryPreview = document.querySelector('#corpus-analysis-concordance-query-preview');
-    let queryChipElements = Array.from(Object.values(this.elements.queryInputFieldInstance.$chips));
-    if (!isNaN(queryChipElements[queryChipElements.length - 1])) {
-      queryChipElements.pop();
-    }
-    this.elements.queryInputFieldContent = [];
-    queryChipElements.forEach(element => {
-
+    let queryInputFieldContent = [];
+    this.elements.queryChipElements.forEach(element => {
       let queryElement = element.dataset.query;
       if (queryElement !== undefined) {
         queryElement = Utils.escape(queryElement);
-        this.elements.queryInputFieldContent.push(queryElement);
       }
+      queryInputFieldContent.push(queryElement);
     });
   
-    let queryString = this.elements.queryInputFieldContent.join(' ');
+    let queryString = queryInputFieldContent.join(' ');
     queryString += ';';
   
     queryPreview.innerHTML = queryString;
     queryPreview.parentNode.classList.toggle('hide', queryString === ';');
   }
 
-  deleteAttr(attr) {
+  deleteChipElement(attr) {
     if (attr.dataset.type === "start-sentence") {
       this.elements.sentenceElement.innerHTML = 'Sentence';
     } else if (attr.dataset.type === "start-entity" || attr.dataset.type === "start-empty-entity") {
       this.elements.entityElement.innerHTML = 'Entity';
     }
-    let queryChipElements = Array.from(Object.values(this.elements.queryInputFieldInstance.$chips));
-    queryChipElements.pop();
-    this.elements.counter -= 1;
-    this.elements.queryInputFieldInstance.deleteChip(queryChipElements.indexOf(attr));
+    this.elements.queryInputField.removeChild(attr);
+    this.updateChipList();
     this.queryPreviewBuilder();
   }
+
+  selectChipElement(attr) {
+    document.querySelectorAll('.chip.teal').forEach(element => {
+        if (element !== attr) {
+          element.classList.remove('teal', 'lighten-2');
+          this.toggleClass(['token-incidence-modifiers'], 'disabled', 'add');
+        }
+    });
+
+    this.toggleClass(['token-incidence-modifiers'], 'disabled', 'toggle');
+    attr.classList.toggle('teal');
+    attr.classList.toggle('lighten-2');
+}
 }
 
diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js
index 8b65619f..742533de 100644
--- a/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js
+++ b/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js
@@ -2,11 +2,32 @@ class StructuralAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQu
   constructor(elements) {
     super(elements);
     this.elements = elements;
+
+    document.querySelectorAll('[data-structural-attr-modal-action-button]').forEach(button => {
+      button.addEventListener('click', () => {
+        this.actionButtonInStrucAttrModalHandler(button.dataset.structuralAttrModalActionButton);
+      });
+    });
+    document.querySelector('.ent-type-selection-action[data-ent-type="any"]').addEventListener('click', () => {
+      this.queryChipFactory('start-empty-entity', 'Entity Start', '<ent>');
+      this.queryChipFactory('end-entity', 'Entity End', '</ent>');
+      this.elements.structuralAttrModal.close();
+    });
+    document.querySelector('.ent-type-selection-action[data-ent-type="english"]').addEventListener('change', (event) => {
+      this.queryChipFactory('start-entity', `Entity Type=${event.target.value}`, `<ent_type="${event.target.value}">`);
+      this.queryChipFactory('end-entity', 'Entity End', '</ent_type>');
+      this.elements.structuralAttrModal.close();
+    });
+
+
   }
 
   actionButtonInStrucAttrModalHandler(action) {
     switch (action) {
       case 'sentence':
+        this.queryChipFactory('start-sentence', 'Sentence Start', '<s>');
+        this.queryChipFactory('end-sentence', 'Sentence End', '</s>');
+        this.elements.structuralAttrModal.close();
         break;
       case 'entity':
         this.toggleClass(['entity-builder'], 'hide', 'toggle');
@@ -20,4 +41,5 @@ class StructuralAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQu
         break;
     }
   }
+
 }
diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js
index bef06116..d887f341 100644
--- a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js
+++ b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js
@@ -3,6 +3,36 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
     super(elements);
     this.elements = elements;
 
+    this.elements.positionalAttrSelection.addEventListener('change', (event) => {
+      this.toggleClass(['word', 'lemma', 'english-pos', 'german-pos', 'simple-pos'], 'hide', 'add');
+      if (event.target.value !== 'empty-token') {
+        this.toggleClass([event.target.value], 'hide', 'remove');
+        this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add');
+        this.resetMaterializeSelection([this.elements.englishPosSelection, this.elements.germanPosSelection, this.elements.simplePosSelection]);
+      }
+      if (event.target.value === 'word' || event.target.value === 'lemma') {
+        this.toggleClass(['input-field-options'], 'hide', 'remove');
+      } else if (event.target.value === 'empty-token'){
+        this.addTokenToQuery();
+      } else {
+        this.toggleClass(['input-field-options'], 'hide', 'add');
+      }
+    });
+
+    // Options for positional attribute selection
+    document.querySelectorAll('.positional-attr-options-action-button[data-options-action]').forEach(button => {
+      button.addEventListener('click', () => {this.actionButtonInOptionSectionHandler(button.dataset.optionsAction);});
+    });
+    document.querySelectorAll('.incidence-modifier-selection[data-incidence-modifier]').forEach(button => {
+      button.addEventListener('click', () => {this.incidenceModifierHandler(button);});
+    });
+    document.querySelectorAll('.n-m-submit-button').forEach(button => {
+      button.addEventListener('click', () => {
+        this.nmSubmitHandler(button.dataset.modalId);
+      });
+    });
+
+    // Eventlistener for kind of token 
     this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();});
     this.elements.wordInput.addEventListener('input', () => {this.optionToggleHandler();});
     this.elements.lemmaInput.addEventListener('input', () => {this.optionToggleHandler();});
@@ -119,7 +149,7 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
           this.disableTokenSubmit();
         } else {
           tokenQueryPrettyText += `simple_pos=${this.elements.simplePosSelection.value}`;
-          tokenQueryCQLText += `simple_pos="${this.elements.simplePosSelection.value}"`;
+          tokenQueryCQLText += `simple_pos='${this.elements.simplePosSelection.value}'`;
           this.elements.simplePosSelection.value = '';
         }
         break;
@@ -149,7 +179,7 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
         input.setSelectionRange(firstIndex, lastIndex);
         break;
       case 'wildcard-char':
-        input.value  += '.';
+        input.value += '.';
         break;
       case 'and':
         this.conditionHandler('and', " & ");
@@ -195,7 +225,8 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu
   nmSubmitHandler(modalId) {
     let modal = document.querySelector(`#${modalId}`);
     let input_n = modal.querySelector('.n-m-input[data-value-type="n"]').value;
-    let input_m = modalId === 'corpus-analysis-concordance-between-nm-modal' ? ',' + modal.querySelector('.n-m-input[data-value-type="m"]').value : '';
+    let input_m = modal.querySelector('.n-m-input[data-value-type="m"]') || undefined;
+    input_m = input_m !== undefined ? ',' + input_m.value : '';
     let input = `${input_n}${input_m}`;
 
     let instance = M.Modal.getInstance(modal);
diff --git a/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 b/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2
index d6b112eb..f0a1133e 100644
--- a/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2
+++ b/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2
@@ -1,6 +1,6 @@
 {% macro card_content(id_prefix) %}
 <div class="row">
-  <form id="corpus-analysis-concordance-form">
+  <form id="corpus-analysis-concordance-expert-mode-form">
     <div class="input-field col s12 m9">
       <i class="material-icons prefix">search</i>
       <input class="validate corpus-analysis-action" id="corpus-analysis-concordance-form-query" name="query" type="text" required pattern=".*\S+.*" placeholder="Type in your query or use the Query Builder on the right"></input>
diff --git a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2
index 21a13f0d..d6452358 100644
--- a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2
+++ b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2
@@ -1,8 +1,8 @@
 {% macro card_content(id_prefix) %}
-<form id="corpus-analysis-concordance-form">
+<form id="corpus-analysis-concordance-query-builder-form">
   <div class="row">
-    <div class="col s9">
-      <div class="chips" id="corpus-analysis-concordance-form-query-builder"></div>
+    <div class="col s9" id="corpus-analysis-concordance-query-builder-input-field-container">
+      <div id="corpus-analysis-concordance-query-builder-input-field"></div>
     </div>
     <div class="input-field col s3">
       <i class="material-icons prefix">arrow_forward</i>
@@ -30,6 +30,7 @@
       <p></p>
       <a class="btn waves-effect waves-light tooltipped modal-trigger" href="#corpus-analysis-concordance-positional-attr-modal" data-position="bottom" data-tooltip="Search for any token, for example a word, a lemma or a part-of-speech tag">Add new token to your query</a>
       <a class="btn waves-effect waves-light tooltipped modal-trigger" href="#corpus-analysis-concordance-structural-attr-modal" data-position="bottom" data-tooltip="Structure your query with structural attributes, for example sentences, entities or annotate the text">Add structural attributes to your query</a>
+      <a class="btn waves-effect waves-light tooltipped dropdown-trigger disabled" data-target="corpus-analysis-concordance-token-incidence-modifiers-dropdown" data-toggle-area="token-incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a>
     </div>
   </div>
   <div class="row">
@@ -42,6 +43,14 @@
     </div>
   </div>
 </form>
+
+<ul id="corpus-analysis-concordance-token-incidence-modifiers-dropdown" class="dropdown-content">
+  {{ incidence_modifiers_dropdown_content("token") }}
+</ul>
+
+{{ exactly_n_modal_content("token") }}
+{{ exactly_nm_modal_content("token") }}
+
 {% endmacro %}
 
 {% macro structural_attribute_modal(id_prefix) %}
@@ -63,10 +72,10 @@
       <p></p>
       <br>
       <div class="row">
-        <a class="btn waves-effect waves-light col s4" id="corpus-analysis-concordance-empty-entity">Add Entity of any type</a>
+        <a class="btn waves-effect waves-light col ent-type-selection-action" data-ent-type="any">Add Entity of any type</a>
         <p class="col s1 l1"></p>
         <div class= "input-field col s3">
-            <select name="englishenttype" id="corpus-analysis-concordance-english-ent-type">
+            <select name="englishenttype" class="ent-type-selection-action" data-ent-type="english">
               <option value="" disabled selected>English ent_type</option>
               <option value="CARDINAL">CARDINAL</option>
               <option value="DATE">DATE</option>
@@ -90,7 +99,7 @@
             <label>Entity Type</label>
         </div>
         <div class= "input-field col s3">
-            <select name="germanenttype" id="corpus-analysis-concordance-german-ent-type">
+            <select name="germanenttype" class="ent-type-selection-action" data-ent-type="german">
               <option value="" disabled selected>German ent_type</option>
               <option value="LOC">LOC</option>
               <option value="MISC">MISC</option>
@@ -347,10 +356,10 @@
         </div>
         <p></p>
         <div class="row">
-          <div class="col s6" data-toggle-area="input-field-options">
+          <div class="col s8" data-toggle-area="input-field-options">
             <a class="btn-small waves-effect waves-light tooltipped positional-attr-options-action-button" data-options-action="wildcard-char" data-position="top" data-tooltip="Look for a variable character (also called wildcard character)">Wildcard character</a>
             <a class="btn-small waves-effect waves-light tooltipped positional-attr-options-action-button" data-options-action="option-group" data-position="top" data-tooltip="Find character sequences from a list of options">Option Group</a>
-            <a class="dropdown-trigger btn-small waves-effect waves-light disabled" href="#" data-target="corpus-analysis-concordance-incidence-modifiers-dropdown" data-toggle-area="incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a>
+            <a class="dropdown-trigger btn-small waves-effect waves-light disabled" href="#" data-target="corpus-analysis-concordance-character-incidence-modifiers-dropdown" data-toggle-area="incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a>
             <span data-toggle-area="ignore-case-checkbox">
               <label>
                 <input type="checkbox" class="filled-in"  id="corpus-analysis-concordance-ignore-case-checkbox"/>
@@ -363,47 +372,61 @@
             <a class="btn-small tooltipped waves-effect waves-light disabled positional-attr-options-action-button" data-options-action="and" data-toggle-area="and" data-position="bottom" data-tooltip="You can add another condition to your token. <br>Both must be fulfilled">and</a>
           </div>
 
-          <ul id="corpus-analysis-concordance-incidence-modifiers-dropdown" class="dropdown-content">
-            <li><a class="tooltipped incidence-modifier-selection" data-token="+" data-incidence-modifier="one-or-more" data-position ="top" data-tooltip="...occurrences of the character/token before">one or more (+)</a></li>
-            <li><a class="tooltipped incidence-modifier-selection" data-token="*" data-incidence-modifier="zero-or-more" data-position ="top" data-tooltip="...occurrences of the character/token before">zero or more (*)</a></li>
-            <li><a class="tooltipped incidence-modifier-selection" data-token="?" data-incidence-modifier="zero-or-one" data-position ="top" data-tooltip="...occurrences of the character/token before">zero or one (?)</a></li>
-            <li><a class="modal-trigger tooltipped" href="#corpus-analysis-concordance-exactly-n-modal" data-position ="top" data-tooltip="...occurrences of the character/token before">exactly n ({n})</a></li>
-            <li><a class="modal-trigger tooltipped" href="#corpus-analysis-concordance-between-nm-modal" data-position ="top" data-tooltip="...occurrences of the character/token before">between n and m ({n,m})</a></li>
+          <ul id="corpus-analysis-concordance-character-incidence-modifiers-dropdown" class="dropdown-content">
+            {{ incidence_modifiers_dropdown_content("character") }}
           </ul>
 
         </div>
       </div>
     </div>
-    <div id="corpus-analysis-concordance-exactly-n-modal" class="modal">
-      <div class="row modal-content">
-        <div class="input-field col s10">
-          <i class="material-icons prefix">mode_edit</i>
-          <input class="n-m-input" placeholder="type in a number for 'n'" type="text" data-value-type="n">
-        </div>
-        <div class="col s2">
-          <p class="btn-floating waves-effect waves-light n-m-submit-button" data-modal-id="corpus-analysis-concordance-exactly-n-modal">
-            <i class="material-icons right">send</i>
-          </p>
-        </div>
-      </div>
+    
+    {{ exactly_n_modal_content("character") }}
+    {{ exactly_nm_modal_content("character") }}
+      
     </div>
+  </div>
+</div>
+{% endmacro %}
 
-    <div id="corpus-analysis-concordance-between-nm-modal" class="modal">
-      <div class="row modal-content">
-        <div class="input-field col s5">
-            <i class="material-icons prefix">mode_edit</i>
-            <input class="n-m-input" placeholder="number for 'n'" type="text" data-value-type="n">
-        </div>
-        <div class="input-field col s5">
-            <i class="material-icons prefix">mode_edit</i>
-            <input class="n-m-input" placeholder="number for 'm'" type="text" data-value-type="m">
-        </div>
-        <div class="col s2">
-          <p class="btn-floating waves-effect waves-light n-m-submit-button" data-modal-id="corpus-analysis-concordance-between-nm-modal">
-            <i class="material-icons right">send</i>
-          </p>
-        </div>
-      </div>
+{% macro incidence_modifiers_dropdown_content(type) %}
+<li><a class="incidence-modifier-selection" data-token="+" data-incidence-modifier="one-or-more">one or more (+)</a></li>
+<li><a class="incidence-modifier-selection" data-token="*" data-incidence-modifier="zero-or-more">zero or more (*)</a></li>
+<li><a class="incidence-modifier-selection" data-token="?" data-incidence-modifier="zero-or-one">zero or one (?)</a></li>
+<li><a class="modal-trigger" href="#corpus-analysis-concordance-exactly-n-{{ type }}-modal">exactly n ({n})</a></li>
+<li><a class="modal-trigger" href="#corpus-analysis-concordance-between-nm-{{ type }}-modal">between n and m ({n,m})</a></li>
+{% endmacro %}
+
+{% macro exactly_n_modal_content(type) %}
+<div id="corpus-analysis-concordance-exactly-n-{{ type }}-modal" class="modal">
+  <div class="row modal-content">
+    <div class="input-field col s10">
+      <i class="material-icons prefix">mode_edit</i>
+      <input class="n-m-input" placeholder="type in a number for 'n'" type="text" data-value-type="n">
+    </div>
+    <div class="col s2">
+      <p class="btn-floating waves-effect waves-light n-m-submit-button" data-modal-id="corpus-analysis-concordance-exactly-n-{{ type }}-modal">
+        <i class="material-icons right">send</i>
+      </p>
+    </div>
+  </div>
+</div>
+{% endmacro %}
+
+{% macro exactly_nm_modal_content(type) %}
+<div id="corpus-analysis-concordance-between-nm-{{ type }}-modal" class="modal">
+  <div class="row modal-content">
+    <div class="input-field col s5">
+        <i class="material-icons prefix">mode_edit</i>
+        <input class="n-m-input" placeholder="number for 'n'" type="text" data-value-type="n">
+    </div>
+    <div class="input-field col s5">
+        <i class="material-icons prefix">mode_edit</i>
+        <input class="n-m-input" placeholder="number for 'm'" type="text" data-value-type="m">
+    </div>
+    <div class="col s2">
+      <p class="btn-floating waves-effect waves-light n-m-submit-button" data-modal-id="corpus-analysis-concordance-between-nm-{{ type }}-modal">
+        <i class="material-icons right">send</i>
+      </p>
     </div>
   </div>
 </div>
-- 
GitLab