class ConcordanceQueryBuilder { constructor() { this.elements = { counter: 0, yourQueryContent: [], queryContent:[], concordanceQueryBuilder: document.querySelector('#concordance-query-builder'), 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 positionalAttrButton: document.querySelector('#positional-attr-button'), positionalAttrArea: document.querySelector('#positional-attr'), positionalAttr: document.querySelector('#token-attr'), structuralAttrButton: document.querySelector('#structural-attr-button'), structuralAttrArea: document.querySelector('#structural-attr'), queryContainer: document.querySelector('#query-container'), buttonPreparer: document.querySelector('#button-preparer'), yourQuery: document.querySelector('#your-query'), insertQueryButton: document.querySelector('#insert-query-button'), queryPreview: document.querySelector('#query-preview'), tokenQuery: document.querySelector('#token-query'), tokenBuilderContent: document.querySelector('#token-builder-content'), tokenSubmitButton: document.querySelector('#token-submit'), extFormQuery: document.querySelector('#concordance-extension-form-query'), dropButton: '', queryBuilderTutorialInfoIcon: document.querySelector('#query-builder-tutorial-info-icon'), tokenTutorialInfoIcon: document.querySelector('#token-tutorial-info-icon'), editTokenTutorialInfoIcon: document.querySelector('#edit-options-tutorial-info-icon'), structuralAttributeTutorialInfoIcon: document.querySelector('#add-structural-attribute-tutorial-info-icon'), generalOptionsQueryBuilderTutorialInfoIcon: document.querySelector('#general-options-query-builder-tutorial-info-icon'), //#endregion QueryBuilder Elements //#region Strucutral Attributes sentence:document.querySelector('#sentence'), entity: document.querySelector('#entity'), textAnnotation: document.querySelector('#text-annotation'), entityBuilder: document.querySelector('#entity-builder'), englishEntType: document.querySelector('#english-ent-type'), germanEntType: document.querySelector('#german-ent-type'), emptyEntity: document.querySelector('#empty-entity'), entityAnyType: false, textAnnotationBuilder: document.querySelector('#text-annotation-builder'), textAnnotationOptions: document.querySelector('#text-annotation-options'), textAnnotationInput: document.querySelector('#text-annotation-input'), textAnnotationSubmit: document.querySelector('#text-annotation-submit'), noValueMetadataMessage: document.querySelector('#no-value-metadata-message'), //#endregion Structural Attributes //#region Token Attributes tokenQueryFilled: false, lemma: document.querySelector('#lemma'), emptyToken: document.querySelector('#empty-token'), word: document.querySelector('#word'), lemma: document.querySelector('#lemma'), pos: document.querySelector('#pos'), simplePosButton: document.querySelector('#simple-pos-button'), incidenceModifiers: document.querySelector('[data-target="incidence-modifiers"]'), or: document.querySelector('#or'), and: document.querySelector('#and'), //#region Word and Lemma Elements wordBuilder: document.querySelector('#word-builder'), lemmaBuilder: document.querySelector('#lemma-builder'), inputOptions: document.querySelector('#input-options'), incidenceModifiersButton: document.querySelector('#incidence-modifiers-button'), conditionContainer: document.querySelector('#condition-container'), wordInput: document.querySelector('#word-input'), lemmaInput: document.querySelector('#lemma-input'), ignoreCaseCheckbox : document.querySelector('#ignore-case-checkbox'), ignoreCase: document.querySelector('input[type="checkbox"]'), wildcardChar: document.querySelector('#wildcard-char'), optionGroup: document.querySelector('#option-group'), //#endregion Word and Lemma Elements //#region posBuilder Elements englishPosBuilder: document.querySelector('#english-pos-builder'), englishPos: document.querySelector('#english-pos'), germanPosBuilder: document.querySelector('#german-pos-builder'), germanPos: document.querySelector('#german-pos'), //#endregion posBuilder Elements //#region simple_posBuilder Elements simplePosBuilder: document.querySelector('#simplepos-builder'), simplePos: document.querySelector('#simple-pos'), //#endregion simple_posBuilder Elements //#region incidence modifiers oneOrMore: document.querySelector('#one-or-more'), zeroOrMore: document.querySelector('#zero-or-more'), zeroOrOne: document.querySelector('#zero-or-one'), exactlyN: document.querySelector('#exactlyN'), betweenNM: document.querySelector('#betweenNM'), nInput: document.querySelector('#n-input'), nSubmit: document.querySelector('#n-submit'), nmInput: document.querySelector('#n-m-input'), mInput: document.querySelector('#m-input'), nmSubmit: document.querySelector('#n-m-submit'), //#endregion incidence modifiers cancelBool: false, noValueMessage: document.querySelector('#no-value-message'), //#endregion Token Attributes } this.elements.closeQueryBuilder.addEventListener('click', () => {this.closeQueryBuilderModal(this.elements.concordanceQueryBuilder);}); this.elements.concordanceQueryBuilderButton.addEventListener('click', () => {this.clearAll();}); this.elements.insertQueryButton.addEventListener('click', () => {this.insertQuery();}); this.elements.positionalAttrButton.addEventListener('click', () => {this.showPositionalAttrArea();}); this.elements.structuralAttrButton.addEventListener('click', () => {this.showStructuralAttrArea();}); //#region Structural Attribute Event Listeners this.elements.sentence.addEventListener('click', () => {this.addSentence();}); this.elements.entity.addEventListener('click', () => {this.addEntity();}); this.elements.textAnnotation.addEventListener('click', () => {this.addTextAnnotation();}); this.elements.englishEntType.addEventListener('change', () => {this.englishEntTypeHandler();}); this.elements.germanEntType.addEventListener('change', () => {this.germanEntTypeHandler();}); this.elements.emptyEntity.addEventListener('click', () => {this.emptyEntityButton();}); this.elements.textAnnotationSubmit.addEventListener('click', () => {this.textAnnotationSubmitHandler();}); //#endregion //#region Token Attribute Event Listeners this.elements.queryBuilderTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#query-builder-tutorial-start');}); this.elements.tokenTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#add-new-token-tutorial');}); this.elements.editTokenTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#edit-options-tutorial');}); this.elements.structuralAttributeTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#add-structural-attribute-tutorial');}); this.elements.generalOptionsQueryBuilderTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#general-options-query-builder');}); this.elements.positionalAttr.addEventListener('change', () => {this.tokenTypeSelector();}); this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();}); this.elements.ignoreCase.addEventListener('change', () => {this.inputOptionHandler(this.elements.ignoreCase);}); this.elements.wildcardChar.addEventListener('click', () => {this.inputOptionHandler(this.elements.wildcardChar);}); this.elements.optionGroup.addEventListener('click', () => {this.inputOptionHandler(this.elements.optionGroup);}); this.elements.oneOrMore.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.oneOrMore);}); this.elements.zeroOrMore.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.zeroOrMore);}); this.elements.zeroOrOne.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.zeroOrOne);}); this.elements.nSubmit.addEventListener('click', () => {this.nSubmitHandler();}); this.elements.nmSubmit.addEventListener('click', () => {this.nmSubmitHandler();}); this.elements.or.addEventListener('click', () => {this.orHandler();}); this.elements.and.addEventListener('click', () => {this.andHandler();}); //#endregion Token Attribute Event Listeners } // ########################################################################## // #################### General Functions ################################### // ########################################################################## //#region General Functions closeQueryBuilderModal(closeInstance) { let instance = M.Modal.getInstance(closeInstance); instance.close(); } showPositionalAttrArea() { this.elements.positionalAttrArea.classList.remove('hide'); this.elements.wordBuilder.classList.remove('hide'); this.elements.inputOptions.classList.remove('hide'); this.elements.incidenceModifiersButton.classList.remove('hide'); this.elements.conditionContainer.classList.remove('hide'); this.elements.ignoreCaseCheckbox.classList.remove('hide'); this.elements.structuralAttrArea.classList.add('hide'); this.elements.lemmaBuilder.classList.add('hide'); this.elements.englishPosBuilder.classList.add('hide'); this.elements.germanPosBuilder.classList.add('hide'); this.elements.simplePosBuilder.classList.add('hide'); this.elements.tokenQueryFilled = false; window.location.href = '#token-builder-content'; // Resets materialize select field to default value let SelectInstance = M.FormSelect.getInstance(this.elements.positionalAttr); SelectInstance.input.value = 'word'; this.elements.positionalAttr.value = 'word'; } showStructuralAttrArea() { this.elements.positionalAttrArea.classList.add('hide'); this.elements.structuralAttrArea.classList.remove('hide'); } queryChipFactory(dataType, prettyQueryText, queryText) { window.location.href = '#query-container'; queryText = Utils.escape(queryText); prettyQueryText = Utils.escape(prettyQueryText); let queryChipElement = Utils.HTMLToElement( ` ${prettyQueryText} close ` ); queryChipElement.addEventListener('click', () => {this.deleteAttr(queryChipElement);}); queryChipElement.addEventListener('dragstart', (event) => { // selects all nodes without target class let queryChips = this.elements.yourQuery.querySelectorAll('.query-component'); // Adds a target chip in front of all draggable childnodes setTimeout(() => { let targetChipElement = Utils.HTMLToElement('Drop here'); for (let element of queryChips) { if (element === queryChipElement.nextSibling) {continue;} let targetChipClone = targetChipElement.cloneNode(true); if (element === queryChipElement) { // If the dragged element is not at the very end, a target chip is also inserted at the end if (queryChips[queryChips.length - 1] !== element) { queryChips[queryChips.length - 1].insertAdjacentElement('afterend', targetChipClone); } } else { element.insertAdjacentElement('beforebegin', targetChipClone); } targetChipClone.addEventListener('dragover', (event) => { event.preventDefault(); }); targetChipClone.addEventListener('dragenter', (event) => { event.preventDefault(); event.target.style.borderStyle = 'solid dotted'; }); targetChipClone.addEventListener('dragleave', (event) => { event.preventDefault(); event.target.style.borderStyle = 'hidden'; }); targetChipClone.addEventListener('drop', (event) => { let dropzone = event.target; dropzone.parentElement.replaceChild(queryChipElement, dropzone); this.queryPreviewBuilder(); }); } }, 0); }); queryChipElement.addEventListener('dragend', (event) => { let targets = document.querySelectorAll('.drop-target'); for (let target of targets) { target.remove(); } }); // Ensures that metadata is always at the end of the query: if (this.elements.yourQuery.lastChild === null || this.elements.yourQuery.lastChild.dataset.type !== 'text-annotation') { this.elements.yourQuery.appendChild(queryChipElement); } else if (this.elements.yourQuery.lastChild.dataset.type === 'text-annotation') { this.elements.yourQuery.insertBefore(queryChipElement, this.elements.yourQuery.lastChild); } this.elements.queryContainer.classList.remove('hide'); this.queryPreviewBuilder(); // Shows a hint about possible functions for editing the query at the first added element in the query if (this.elements.yourQuery.childNodes.length === 1) { app.flash('You can edit your query by deleting individual elements or moving them via drag and drop.'); } } queryPreviewBuilder() { this.elements.yourQueryContent = []; for (let element of this.elements.yourQuery.childNodes) { let queryElement = decodeURI(element.dataset.query); queryElement = Utils.escape(queryElement); if (queryElement !== 'undefined') { this.elements.yourQueryContent.push(queryElement); } } let queryString = this.elements.yourQueryContent.join(' '); queryString += ';'; this.elements.queryPreview.innerHTML = queryString; } deleteAttr(attr) { this.elements.yourQuery.removeChild(attr); if (attr.dataset.type === "start-sentence") { this.elements.sentence.innerHTML = 'Sentence'; } else if (attr.dataset.type === "start-entity" || attr.dataset.type === "start-empty-entity") { this.elements.entity.innerHTML = 'Entity'; } this.elements.counter -= 1; if (this.elements.counter === 0) { this.elements.queryContainer.classList.add('hide'); } this.queryPreviewBuilder(); } 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) { 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; } } // Checks if the same number of opening and closing tags (entity and sentence) are present. Depending on what is missing, the corresponding error message is ejected if (sentenceCounter > sentenceEndCounter) { app.flash('Please add the closing sentence tag', 'error'); this.elements.valueValidator = false; } else if (sentenceCounter < sentenceEndCounter) { app.flash('Please remove the closing sentence tag', 'error'); this.elements.valueValidator = false; } if (entityCounter > entityEndCounter) { app.flash('Please add the closing entity tag', 'error'); this.elements.valueValidator = false; } else if (entityCounter < entityEndCounter) { app.flash('Please remove the closing entity tag', 'error'); this.elements.valueValidator = false; } } clearAll() { // Everything is reset. let instance = M.Tooltip.getInstance(this.elements.queryBuilderTutorialInfoIcon); this.hideEverything(); this.elements.counter = 0; this.elements.concordanceQueryBuilder.classList.remove('modal-close'); this.elements.positionalAttrArea.classList.add('hide'); this.elements.structuralAttrArea.classList.add('hide'); this.elements.yourQuery.innerHTML = ''; this.elements.queryContainer.classList.add('hide'); this.elements.entity.innerHTML = 'Entity'; this.elements.sentence.innerHTML = 'Sentence'; // If the Modal is open after 5 seconds for 5 seconds (with 'instance'), a message is displayed indicating that further information can be obtained via the question mark icon instance.tooltipEl.style.background = '#98ACD2'; instance.tooltipEl.style.borderTop = 'solid 4px #0064A3'; instance.tooltipEl.style.padding = '10px'; instance.tooltipEl.style.color = 'black'; setTimeout(() => { let modalInstance = M.Modal.getInstance(this.elements.concordanceQueryBuilder); if (modalInstance.isOpen) { instance.open(); setTimeout(() => { instance.close(); }, 5000); } }, 5000); } tutorialIconHandler(id) { setTimeout(() => { window.location.href= id; }, 0); } //#endregion General Functions // ########################################################################## // ############## Token Attribute Builder Functions ######################### // ########################################################################## //#region Token Attribute Builder Functions //#region General functions of the Token Builder tokenTypeSelector() { this.hideEverything(); switch (this.elements.positionalAttr.value) { case 'word': this.wordBuilder(); break; case 'lemma': this.lemmaBuilder(); break; case 'english-pos': this.englishPosHandler(); break; case 'german-pos': this.germanPosHandler(); break; case 'simple-pos-button': this.simplePosBuilder(); break; case 'empty-token': this.emptyTokenHandler(); break; default: this.wordBuilder(); break; } } hideEverything() { this.elements.wordBuilder.classList.add('hide'); this.elements.lemmaBuilder.classList.add('hide'); this.elements.ignoreCaseCheckbox.classList.add('hide'); this.elements.inputOptions.classList.add('hide'); this.elements.incidenceModifiersButton.classList.add('hide'); this.elements.conditionContainer.classList.add('hide'); this.elements.englishPosBuilder.classList.add('hide'); this.elements.germanPosBuilder.classList.add('hide'); this.elements.simplePosBuilder.classList.add('hide'); this.elements.entityBuilder.classList.add('hide'); this.elements.textAnnotationBuilder.classList.add('hide'); } tokenChipFactory(prettyQueryText, tokenText) { tokenText = encodeURI(tokenText); let builderElement; let queryChipElement; builderElement = document.createElement('div'); builderElement.innerHTML = `