From 0009a0bacfe17a2e30ea78ec32634cb5317294c6 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Thu, 20 Oct 2022 15:52:25 +0200 Subject: [PATCH] Query Builder implementation --- app/static/js/CorpusAnalysis/QueryBuilder.js | 120 +++--- .../analyse_corpus.concordance.html.j2 | 2 +- app/templates/corpora/analyse_corpus.html.j2 | 356 ++++++++++++++++++ 3 files changed, 427 insertions(+), 51 deletions(-) diff --git a/app/static/js/CorpusAnalysis/QueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder.js index e16a1e41..f9a10256 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder.js @@ -12,6 +12,7 @@ class ConcordanceQueryBuilder { concordanceQueryBuilderButton: document.querySelector('#concordance-query-builder-button'), closeQueryBuilder: document.querySelector('#close-query-builder'), queryBuilderTutorialModal: document.querySelector('#query-builder-tutorial-modal'), + valueValidator: true, //#region QueryBuilder Elements @@ -206,7 +207,6 @@ class ConcordanceQueryBuilder { } buttonfactory(dataType, prettyText, queryText) { - window.location.href = '#query-container'; this.elements.counter += 1; queryText = encodeURI(queryText); @@ -226,7 +226,6 @@ class ConcordanceQueryBuilder { } else if (this.elements.yourQuery.lastChild.dataset.type === 'text-annotation') { this.elements.yourQuery.insertBefore(buttonElement, this.elements.yourQuery.lastChild); } - this.elements.queryContainer.classList.remove('hide'); this.queryPreviewBuilder(); @@ -252,25 +251,30 @@ class ConcordanceQueryBuilder { //#region Drag&Drop Events dragStartHandler(event) { + // Creates element with the class 'target' and all necessary drop functions, in which drop content can be released this.elements.dropButton = event.target; let targetChip = `
Drop here
`.trim(); + // selects all nodes without target class let childNodes = this.elements.yourQuery.querySelectorAll('div:not(.target)'); + // Adds a target chip in front of all draggable childnodes setTimeout(() => { for (let element of childNodes) { - if (element === event.target) { - continue; - } else if (element === event.target.nextSibling) { + if (element === this.elements.dropButton) { + // If the dragged element is not at the very end, a target chip is also inserted at the end + if (childNodes[childNodes.length - 1] !== element) { + childNodes[childNodes.length - 1].insertAdjacentHTML('afterend', targetChip); + } + } else if (element === this.elements.dropButton.nextSibling) { continue; } else { element.insertAdjacentHTML('beforebegin', targetChip) } } - childNodes[childNodes.length-1].insertAdjacentHTML('afterend', targetChip); },0); } @@ -297,18 +301,6 @@ class ConcordanceQueryBuilder { dropHandler(event) { let dropzone = event.target; - - for (let i = 0; i < dropzone.parentElement.childNodes.length; i++) { - if (dropzone === dropzone.parentElement.childNodes[i]) { - nodeIndex = i; - } - } - for (let i = 0; i < dropzone.parentElement.childNodes.length; i++) { - if (this.elements.dropButton === dropzone.parentElement.childNodes[i]) { - draggedElementIndex = i; - } - } - dropzone.parentElement.replaceChild(this.elements.dropButton, dropzone); this.queryPreviewBuilder(); } @@ -316,7 +308,6 @@ class ConcordanceQueryBuilder { queryPreviewBuilder() { this.elements.yourQueryContent = []; - for (let element of this.elements.yourQuery.childNodes) { let queryElement = decodeURI(element.dataset.query); if (queryElement.includes('<')) { @@ -349,19 +340,48 @@ class ConcordanceQueryBuilder { insertQuery() { this.elements.yourQueryContent = []; + this.validateValue(); + if (this.elements.valueValidator === true) { + for (let element of this.elements.yourQuery.childNodes) { + let queryElement = decodeURI(element.dataset.query); + if (queryElement !== 'undefined') { + this.elements.yourQueryContent.push(queryElement); + } + } + let queryString = this.elements.yourQueryContent.join(' '); + queryString += ';'; + + this.elements.concordanceQueryBuilder.classList.add('modal-close'); + this.elements.extFormQuery.value = queryString; + } + } + + validateValue() { + this.elements.valueValidator = true; + let sentenceCounter = 0; + let sentenceEndCounter = 0; + let entityCounter = 0; + let entityEndCounter = 0; for (let element of this.elements.yourQuery.childNodes) { - let queryElement = decodeURI(element.dataset.query); - if (queryElement !== 'undefined') { - this.elements.yourQueryContent.push(queryElement); + if (element.dataset.type === 'start-sentence') { + sentenceCounter += 1; + }else if (element.dataset.type === 'end-sentence') { + sentenceEndCounter += 1; + }else if (element.dataset.type === 'start-entity' || element.dataset.type === 'start-empty-entity') { + entityCounter += 1; + }else if (element.dataset.type === 'end-entity') { + entityEndCounter += 1; } } - - let queryString = this.elements.yourQueryContent.join(' '); - queryString += ';'; - - this.elements.concordanceQueryBuilder.classList.add('modal-close'); - this.elements.extFormQuery.value = queryString; + if (sentenceCounter !== sentenceEndCounter) { + app.flash('Please add a closing sentence tag', 'error'); + this.elements.valueValidator = false; + } + if (entityCounter !== entityEndCounter) { + app.flash('Please add a closing entity tag', 'error'); + this.elements.valueValidator = false; + } } clearAll() { @@ -506,7 +526,7 @@ class ConcordanceQueryBuilder { this.disableTokenSubmit(); } else { tokenQueryContent += `word=${this.elements.wordInput.value}${c}`; - tokenQueryText += `word='${this.elements.wordInput.value}'${c}`; + tokenQueryText += `word="${this.elements.wordInput.value}"${c}`; this.elements.wordInput.value = ''; } break; @@ -515,7 +535,7 @@ class ConcordanceQueryBuilder { this.disableTokenSubmit(); } else { tokenQueryContent += `lemma=${this.elements.lemmaInput.value}${c}`; - tokenQueryText += `lemma='${this.elements.lemmaInput.value}'${c}`; + tokenQueryText += `lemma="${this.elements.lemmaInput.value}"${c}`; this.elements.lemmaInput.value = ''; } break; @@ -524,7 +544,7 @@ class ConcordanceQueryBuilder { this.disableTokenSubmit(); } else { tokenQueryContent += `pos=${this.elements.englishPos.value}`; - tokenQueryText += `pos='${this.elements.englishPos.value}'`; + tokenQueryText += `pos="${this.elements.englishPos.value}"`; this.elements.englishPos.value = ''; } break; @@ -533,7 +553,7 @@ class ConcordanceQueryBuilder { this.disableTokenSubmit(); } else { tokenQueryContent += `pos=${this.elements.germanPos.value}`; - tokenQueryText += `pos='${this.elements.germanPos.value}'`; + tokenQueryText += `pos="${this.elements.germanPos.value}"`; this.elements.germanPos.value = ''; } break; @@ -542,7 +562,7 @@ class ConcordanceQueryBuilder { this.disableTokenSubmit(); } else { tokenQueryContent += `simple_pos=${this.elements.simplePos.value}`; - tokenQueryText += `simple_pos='${this.elements.simplePos.value}'`; + tokenQueryText += `simple_pos="${this.elements.simplePos.value}"`; this.elements.simplePos.value = ''; } break; @@ -557,7 +577,7 @@ class ConcordanceQueryBuilder { // Square brackets are added only if it is not an empty token (where they are already present). if (emptyTokenCheck === false) { tokenQueryText = '[' + tokenQueryText + ']'; - } + } this.buttonfactory('token', tokenQueryContent, tokenQueryText); this.hideEverything(); this.elements.positionalAttrArea.classList.add('hide'); @@ -686,21 +706,21 @@ class ConcordanceQueryBuilder { break; case 'english-pos': this.elements.tokenQueryFilled = true; - this.tokenButtonfactory(`pos=${this.elements.englishPos.value}`, `pos='${this.elements.englishPos.value}'`); + this.tokenButtonfactory(`pos=${this.elements.englishPos.value}`, `pos="${this.elements.englishPos.value}"`); this.tokenButtonfactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}'); this.elements.englishPosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); break; case 'german-pos': this.elements.tokenQueryFilled = true; - this.tokenButtonfactory(`pos=${this.elements.germanPos.value}`, `pos='${this.elements.germanPos.value}'`); + this.tokenButtonfactory(`pos=${this.elements.germanPos.value}`, `pos="${this.elements.germanPos.value}"`); this.tokenButtonfactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}'); this.elements.germanPosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); break; case 'simple-pos-button': this.elements.tokenQueryFilled = true; - this.tokenButtonfactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos='${this.elements.simplePos.value}'`); + this.tokenButtonfactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos="${this.elements.simplePos.value}"`); this.tokenButtonfactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}'); this.elements.simplePosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); @@ -727,21 +747,21 @@ class ConcordanceQueryBuilder { break; case 'english-pos': this.elements.tokenQueryFilled = true; - this.tokenButtonfactory(`pos=${this.elements.englishPos.value}`, `pos='${this.elements.englishPos.value}'`); + this.tokenButtonfactory(`pos=${this.elements.englishPos.value}`, `pos="${this.elements.englishPos.value}"`); this.tokenButtonfactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`); this.elements.englishPosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); break; case 'german-pos': this.elements.tokenQueryFilled = true; - this.tokenButtonfactory(`pos=${this.elements.germanPos.value}`, `pos='${this.elements.germanPos.value}'`); + this.tokenButtonfactory(`pos=${this.elements.germanPos.value}`, `pos="${this.elements.germanPos.value}"`); this.tokenButtonfactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`); this.elements.germanPosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); break; case 'simple-pos-button': this.elements.tokenQueryFilled = true; - this.tokenButtonfactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos='${this.elements.simplePos.value}'`); + this.tokenButtonfactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos="${this.elements.simplePos.value}"`); this.tokenButtonfactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`); this.elements.simplePosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); @@ -759,19 +779,19 @@ class ConcordanceQueryBuilder { if (this.elements.positionalAttr.value === 'empty-token') { this.tokenButtonfactory(elem.innerText, elem.dataset.token); } else if (this.elements.positionalAttr.value === 'english-pos') { - this.tokenButtonfactory(`pos=${this.elements.englishPos.value}`, `pos='${this.elements.englishPos.value}'`); + this.tokenButtonfactory(`pos=${this.elements.englishPos.value}`, `pos="${this.elements.englishPos.value}"`); this.tokenButtonfactory(elem.innerText, elem.dataset.token); this.elements.englishPosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); this.elements.tokenQueryFilled = true; } else if (this.elements.positionalAttr.value === 'german-pos') { - this.tokenButtonfactory(`pos=${this.elements.germanPos.value}`, `pos='${this.elements.germanPos.value}'`); + this.tokenButtonfactory(`pos=${this.elements.germanPos.value}`, `pos="${this.elements.germanPos.value}"`); this.tokenButtonfactory(elem.innerText, elem.dataset.token); this.elements.germanPosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); this.elements.tokenQueryFilled = true; } else if (this.elements.positionalAttr.value === 'simple-pos-button') { - this.tokenButtonfactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos='${this.elements.simplePos.value}'`); + this.tokenButtonfactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos="${this.elements.simplePos.value}"`); this.tokenButtonfactory(elem.innerText, elem.dataset.token); this.elements.simplePosBuilder.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); @@ -813,27 +833,27 @@ class ConcordanceQueryBuilder { switch (this.elements.positionalAttr.value) { case 'word': tokenQueryContent = `word=${this.elements.wordInput.value}${c}`; - tokenQueryText = `word='${this.elements.wordInput.value}'${c}`; + tokenQueryText = `word="${this.elements.wordInput.value}"${c}`; this.elements.wordInput.value = ''; break; case 'lemma': tokenQueryContent = `lemma=${this.elements.lemmaInput.value}${c}`; - tokenQueryText = `word='${this.elements.lemmaInput.value}'${c}`; + tokenQueryText = `lemma="${this.elements.lemmaInput.value}"${c}`; this.elements.lemmaInput.value = ''; break; case 'english-pos': tokenQueryContent = `pos=${this.elements.englishPos.value}`; - tokenQueryText = `pos='${this.elements.englishPos.value}'`; + tokenQueryText = `pos="${this.elements.englishPos.value}"`; this.elements.englishPos.value = ''; break; case 'german-pos': tokenQueryContent = `pos=${this.elements.germanPos.value}`; - tokenQueryText = `pos='${this.elements.germanPos.value}'`; + tokenQueryText = `pos="${this.elements.germanPos.value}"`; this.elements.germanPos.value = ''; break; case 'simple-pos-button': tokenQueryContent = `simple_pos=${this.elements.simplePos.value}`; - tokenQueryText = `simple_pos='${this.elements.simplePos.value}'`; + tokenQueryText = `simple_pos="${this.elements.simplePos.value}"`; this.elements.simplePos.value = ''; break; default: @@ -888,7 +908,7 @@ class ConcordanceQueryBuilder { } englishEntTypeHandler() { - this.buttonfactory('start-entity', 'Entity Type=' + this.elements.englishEntType.value, ''); + this.buttonfactory('start-entity', 'Entity Type=' + this.elements.englishEntType.value, ''); this.elements.entity.innerHTML = 'End Entity'; this.hideEverything(); this.elements.entityAnyType = false; @@ -900,7 +920,7 @@ class ConcordanceQueryBuilder { } germanEntTypeHandler() { - this.buttonfactory('start-entity', 'Entity Type=' + this.elements.germanEntType.value, ''); + this.buttonfactory('start-entity', 'Entity Type=' + this.elements.germanEntType.value, ''); this.elements.entity.innerHTML = 'End Entity'; this.hideEverything(); this.elements.entityAnyType = false; diff --git a/app/templates/corpora/analyse_corpus.concordance.html.j2 b/app/templates/corpora/analyse_corpus.concordance.html.j2 index 0fc14597..d223ca30 100644 --- a/app/templates/corpora/analyse_corpus.concordance.html.j2 +++ b/app/templates/corpora/analyse_corpus.concordance.html.j2 @@ -64,7 +64,7 @@

 

- + build Query builder diff --git a/app/templates/corpora/analyse_corpus.html.j2 b/app/templates/corpora/analyse_corpus.html.j2 index 3c59bca4..50c7d71c 100644 --- a/app/templates/corpora/analyse_corpus.html.j2 +++ b/app/templates/corpora/analyse_corpus.html.j2 @@ -248,6 +248,361 @@
+ + + + + {% endblock modals %} {% block scripts %} @@ -256,6 +611,7 @@ const corpusAnalysisApp = new CorpusAnalysisApp({{ corpus.hashid|tojson }}); const corpusAnalysisConcordance = new CorpusAnalysisConcordance(corpusAnalysisApp); const corpusAnalysisReader = new CorpusAnalysisReader(corpusAnalysisApp); +const concordanceQueryBuilder = new ConcordanceQueryBuilder(); corpusAnalysisApp.init();