From afcb890ccfaf00026731694c399bda55435125a9 Mon Sep 17 00:00:00 2001 From: Inga Kirschnick Date: Wed, 22 Nov 2023 12:50:08 +0100 Subject: [PATCH] Element Target drag&drop + small improvements --- .../query-builder/element-references.js | 2 + .../query-builder/query-builder.js | 175 ++++++++++-------- .../query_builder/_expert_mode.html.j2 | 11 ++ .../query_builder/_query_builder.html.j2 | 17 +- 4 files changed, 119 insertions(+), 86 deletions(-) diff --git a/app/static/js/corpus-analysis/query-builder/element-references.js b/app/static/js/corpus-analysis/query-builder/element-references.js index 29465112..1b9f384e 100644 --- a/app/static/js/corpus-analysis/query-builder/element-references.js +++ b/app/static/js/corpus-analysis/query-builder/element-references.js @@ -3,8 +3,10 @@ nopaque.corpus_analysis.query_builder.ElementReferences = class ElementReference // General Elements this.queryInputField = document.querySelector('#corpus-analysis-concordance-query-builder-input-field'); this.queryChipElements = []; + this.queryElementTarget = document.querySelector('.query-element-target') this.editingModusOn = false; this.editedQueryChipElementIndex = undefined; + this.deleteQueryButton = document.querySelector('#corpus-analysis-concordance-delete-query-button'); // Structural Attribute Builder Elements this.structuralAttrModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-structural-attr-modal')); diff --git a/app/static/js/corpus-analysis/query-builder/query-builder.js b/app/static/js/corpus-analysis/query-builder/query-builder.js index 5098b684..702037f7 100644 --- a/app/static/js/corpus-analysis/query-builder/query-builder.js +++ b/app/static/js/corpus-analysis/query-builder/query-builder.js @@ -2,25 +2,13 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { constructor() { this.elements = new nopaque.corpus_analysis.query_builder.ElementReferences(); - this.incidenceModifierEventListeners(); - this.nAndMInputSubmitEventListeners(); + this.addEventListenersToQueryElementTarget(); + this.addEventListenersToIncidenceModifier(); + this.addEventListenersToNAndMInputSubmit(); - let queryBuilderDisplay = document.querySelector("#corpus-analysis-concordance-query-builder-display"); - let expertModeDisplay = document.querySelector("#corpus-analysis-concordance-expert-mode-display"); - let expertModeSwitch = document.querySelector("#corpus-analysis-concordance-expert-mode-switch"); - - expertModeSwitch.addEventListener("change", () => { - const isChecked = expertModeSwitch.checked; - if (isChecked) { - queryBuilderDisplay.classList.add("hide"); - expertModeDisplay.classList.remove("hide"); - this.switchToExpertModeParser(); - } else { - queryBuilderDisplay.classList.remove("hide"); - expertModeDisplay.classList.add("hide"); - this.switchToQueryBuilderParser(); - } - }); + this.elements.deleteQueryButton.addEventListener('click', () => {this.resetQueryInputField()}); + + this.expertModeQueryBuilderSwitchHandler(); this.extensions = { structuralAttributeBuilderFunctions: new nopaque.corpus_analysis.query_builder.StructuralAttributeBuilderFunctions(this), @@ -28,6 +16,38 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { }; } + addEventListenersToQueryElementTarget() { + this.elements.queryElementTarget.addEventListener('click', () => { + this.elements.positionalAttrModal.open(); + }); + this.elements.queryElementTarget.addEventListener('dragstart', this.handleDragStart.bind(this, this.elements.queryElementTarget)); + this.elements.queryElementTarget.addEventListener('dragend', this.handleDragEnd); + } + + addEventListenersToIncidenceModifier() { + // Eventlisteners for the incidence modifiers. There are two different types of incidence modifiers: token and character incidence modifiers. + document.querySelectorAll('.incidence-modifier-selection').forEach(button => { + let dropdownId = button.parentNode.parentNode.id; + if (dropdownId === 'corpus-analysis-concordance-token-incidence-modifiers-dropdown') { + button.addEventListener('click', () => this.tokenIncidenceModifierHandler(button.dataset.token, button.innerHTML)); + } else if (dropdownId === 'corpus-analysis-concordance-character-incidence-modifiers-dropdown') { + button.addEventListener('click', () => this.extensions.tokenAttributeBuilderFunctions.characterIncidenceModifierHandler(button)); + } + }); + } + + addEventListenersToNAndMInputSubmit() { + // Eventlisteners for the submit of n- and m-values of the incidence modifier modal for "exactly n" or "between n and m". + document.querySelectorAll('.n-m-submit-button').forEach(button => { + let modalId = button.dataset.modalId; + if (modalId === 'corpus-analysis-concordance-exactly-n-token-modal' || modalId === 'corpus-analysis-concordance-between-nm-token-modal') { + button.addEventListener('click', () => this.tokenNMSubmitHandler(modalId)); + } else if (modalId === 'corpus-analysis-concordance-exactly-n-character-modal' || modalId === 'corpus-analysis-concordance-between-nm-character-modal') { + button.addEventListener('click', () => this.extensions.tokenAttributeBuilderFunctions.characterNMSubmitHandler(modalId)); + } + }); + } + toggleClass(elements, className, action) { elements.forEach(element => { document.querySelector(`[data-toggle-area="${element}"]`).classList[action](className); @@ -36,26 +56,27 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { resetQueryInputField() { this.elements.queryInputField.innerHTML = ''; - this.addPlaceholder(); + this.addQueryElementTarget(); this.updateChipList(); this.queryPreviewBuilder(); } + addQueryElementTarget() { + let queryElementTarget = nopaque.Utils.HTMLToElement( + ` + + add + + ` + ); + this.elements.queryInputField.appendChild(queryElementTarget); + this.elements.queryElementTarget = queryElementTarget; + this.addEventListenersToQueryElementTarget(); + } + updateChipList() { this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.query-component'); } - - removePlaceholder() { - let placeholder = this.elements.queryInputField.querySelector('#corpus-analysis-concordance-query-builder-input-field-placeholder'); - if (placeholder && this.elements.queryInputField !== undefined) { - this.elements.queryInputField.innerHTML = ''; - } - } - - addPlaceholder() { - let placeholder = nopaque.Utils.HTMLToElement('Click on a button to add a query component'); - this.elements.queryInputField.appendChild(placeholder); - } resetMaterializeSelection(selectionElements, value = "default") { selectionElements.forEach(selectionElement => { @@ -89,32 +110,32 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { ` ${prettyQueryText}${isEditable ? 'edit': ''} - ${isClosingTag ? 'lock_open' : 'close'} + ${isClosingTag ? '' : 'close'} ` ); this.addActionListeners(queryChipElement); queryChipElement.addEventListener('dragstart', this.handleDragStart.bind(this, queryChipElement)); queryChipElement.addEventListener('dragend', this.handleDragEnd); - - // Ensures that metadata is always at the end of the query and if an index is given, inserts the query chip at the given index and if there is a closing tag, inserts the query chip before the closing tag. - this.removePlaceholder(); - if (!index) { - let closingTagElement = this.elements.queryInputField.querySelector('[data-closing-tag="true"]'); - if (closingTagElement) { - index = Array.from(this.elements.queryInputField.children).indexOf(closingTagElement); - } - } - if (index) { - this.elements.queryInputField.insertBefore(queryChipElement, this.elements.queryChipElements[index]); + // If an index is given, inserts the query chip after the given index (only relevant for Incidence Modifier) and if there is a closing tag, inserts the query chip before the closing tag. + if (index !== null) { + this.updateChipList(); + this.elements.queryChipElements[index].after(queryChipElement); } else { - this.elements.queryInputField.appendChild(queryChipElement); + this.elements.queryInputField.insertBefore(queryChipElement, this.elements.queryElementTarget); + } + if (isClosingTag) { + this.moveQueryElementTarget(queryChipElement); } this.updateChipList(); this.queryPreviewBuilder(); } + moveQueryElementTarget(element) { + this.elements.queryInputField.insertBefore(this.elements.queryElementTarget, element); + } + addActionListeners(queryChipElement) { let notQuantifiableDataTypes = ['start-sentence', 'end-sentence', 'start-entity', 'start-empty-entity', 'end-entity', 'token-incidence-modifier']; queryChipElement.addEventListener('click', (event) => { @@ -154,20 +175,13 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { } } - lockClosingChipElement(queryChipElement) { - queryChipElement.dataset.closingTag = 'false'; - let lockIcon = queryChipElement.querySelector('[data-chip-action="lock"]'); - lockIcon.textContent = 'lock'; - // TODO: Write unlock-Function? - lockIcon.dataset.chipAction = 'unlock'; - } - deleteChipElement(attr) { let elementIndex = Array.from(this.elements.queryInputField.children).indexOf(attr); switch (attr.dataset.type) { case 'start-sentence': this.deleteClosingTagHandler(elementIndex, 'end-sentence'); break; + case 'start-empty-entity': case 'start-entity': this.deleteClosingTagHandler(elementIndex, 'end-entity'); break; @@ -180,9 +194,6 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { break; } this.elements.queryInputField.removeChild(attr); - if (this.elements.queryInputField.children.length === 0) { - this.addPlaceholder(); - } this.updateChipList(); this.queryPreviewBuilder(); } @@ -217,13 +228,17 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { let targetChipClone = targetChipElement.cloneNode(true); element.insertAdjacentElement('afterend', targetChipClone); - + //TODO: Change to two different functions for drag and drop this.addDragDropListeners(targetChipClone, queryChipElement); } }, 0); } - handleDragEnd() { + handleDragEnd(event) { + // is called when a query chip is dropped. It removes the dropzones and initializes the tooltips if the dragged element is the query element target. + if (event.target.classList.contains('query-element-target')) { + M.Tooltip.init(event.target); + } document.querySelectorAll('.drop-target').forEach(target => target.remove()); } @@ -292,8 +307,8 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { tokenIncidenceModifierHandler(incidenceModifier, incidenceModifierPretty) { // Adds a token incidence modifier to the query input field. let selectedChip = this.elements.queryInputField.querySelector('.chip.teal'); - let selectedChipIndex = Array.from(this.elements.queryInputField.children).indexOf(selectedChip); - this.submitQueryChipElement('token-incidence-modifier', incidenceModifierPretty, incidenceModifier, selectedChipIndex+1); + let selectedChipIndex = Array.from(this.elements.queryChipElements).indexOf(selectedChip); + this.submitQueryChipElement('token-incidence-modifier', incidenceModifierPretty, incidenceModifier, selectedChipIndex); this.selectChipElement(selectedChip); } @@ -315,26 +330,27 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { this.tokenIncidenceModifierHandler(input, pretty_input); } - incidenceModifierEventListeners() { - // Eventlisteners for the incidence modifiers. There are two different types of incidence modifiers: token and character incidence modifiers. - document.querySelectorAll('.incidence-modifier-selection').forEach(button => { - let dropdownId = button.parentNode.parentNode.id; - if (dropdownId === 'corpus-analysis-concordance-token-incidence-modifiers-dropdown') { - button.addEventListener('click', () => this.tokenIncidenceModifierHandler(button.dataset.token, button.innerHTML)); - } else if (dropdownId === 'corpus-analysis-concordance-character-incidence-modifiers-dropdown') { - button.addEventListener('click', () => this.extensions.tokenAttributeBuilderFunctions.characterIncidenceModifierHandler(button)); - } + expertModeQueryBuilderSwitchHandler() { + let queryBuilderDisplay = document.querySelector("#corpus-analysis-concordance-query-builder-display"); + let expertModeDisplay = document.querySelector("#corpus-analysis-concordance-expert-mode-display"); + let expertModeSwitch = document.querySelector("#corpus-analysis-concordance-expert-mode-switch"); + let submitModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-switch-to-query-builder-submit-modal')); + + let confirmSwitchToQueryBuilderButton = document.querySelector('.switch-action[data-switch-action="confirm"]'); + confirmSwitchToQueryBuilderButton.addEventListener("click", () => { + queryBuilderDisplay.classList.remove("hide"); + expertModeDisplay.classList.add("hide"); + this.switchToQueryBuilderParser(); }); - } - nAndMInputSubmitEventListeners() { - // Eventlisteners for the submit of n- and m-values of the incidence modifier modal for "exactly n" or "between n and m". - document.querySelectorAll('.n-m-submit-button').forEach(button => { - let modalId = button.dataset.modalId; - if (modalId === 'corpus-analysis-concordance-exactly-n-token-modal' || modalId === 'corpus-analysis-concordance-between-nm-token-modal') { - button.addEventListener('click', () => this.tokenNMSubmitHandler(modalId)); - } else if (modalId === 'corpus-analysis-concordance-exactly-n-character-modal' || modalId === 'corpus-analysis-concordance-between-nm-character-modal') { - button.addEventListener('click', () => this.extensions.tokenAttributeBuilderFunctions.characterNMSubmitHandler(modalId)); + expertModeSwitch.addEventListener("change", () => { + const isChecked = expertModeSwitch.checked; + if (isChecked) { + queryBuilderDisplay.classList.add("hide"); + expertModeDisplay.classList.remove("hide"); + this.switchToExpertModeParser(); + } else { + submitModal.open(); } }); } @@ -353,7 +369,7 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { let expertModeInputFieldValue = document.querySelector('#corpus-analysis-concordance-form-query').value; let chipElements = this.parseTextToChip(expertModeInputFieldValue); let closingTagElements = ['end-sentence', 'end-entity']; - let editableElements = ['start-entity', 'token']; + let editableElements = ['start-entity', 'token']; for (let chipElement of chipElements) { let isClosingTag = closingTagElements.includes(chipElement['type']); let isEditable = editableElements.includes(chipElement['type']); @@ -361,9 +377,6 @@ nopaque.corpus_analysis.query_builder.QueryBuilder = class QueryBuilder { isEditable = false; } this.submitQueryChipElement(chipElement['type'], chipElement['pretty'], chipElement['query'], null, isClosingTag, isEditable); - if (isClosingTag) { - this.lockClosingChipElement(this.elements.queryChipElements[this.elements.queryChipElements.length-1]); - } } } 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 7ebf4643..9926b367 100644 --- a/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 +++ b/app/templates/corpora/_analysis/query_builder/_expert_mode.html.j2 @@ -23,4 +23,15 @@ + + {% endmacro %} 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 4953e41c..6baecdda 100644 --- a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 +++ b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 @@ -1,11 +1,18 @@ {% macro card_content(id_prefix) %}