nopaque.corpus_analysis.query_builder.TokenAttributeBuilderFunctions = class TokenAttributeBuilderFunctions { constructor(app) { this.app = app; this.elements = app.elements; this.elements.positionalAttrSelection.addEventListener('change', () => { this.preparePositionalAttrModal(); }); // 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);}); }); this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();}); this.elements.positionalAttrModal = M.Modal.init( document.querySelector('#corpus-analysis-concordance-positional-attr-modal'), { onOpenStart: () => { this.preparePositionalAttrModal(); }, onCloseStart: () => { this.resetPositionalAttrModal(); } } ); } resetPositionalAttrModal() { let originalSelectionList = ` <option value="word" selected>word</option> <option value="lemma" >lemma</option> <option value="english-pos">english pos</option> <option value="german-pos">german pos</option> <option value="simple_pos">simple_pos</option> <option value="empty-token">empty token</option> `; this.elements.positionalAttrSelection.innerHTML = originalSelectionList; this.elements.tokenQuery.innerHTML = ''; this.elements.tokenBuilderContent.innerHTML = ''; this.app.toggleClass(['input-field-options'], 'hide', 'remove'); this.app.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add'); this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], "word"); this.elements.ignoreCaseCheckbox.checked = false; this.elements.editingModusOn = false; this.elements.editedQueryChipElementIndex = undefined; } actionButtonInOptionSectionHandler(elem) { let input = this.tokenInputCheck(this.elements.tokenBuilderContent); switch (elem) { case 'option-group': input.value += '(option1|option2)'; let firstIndex = input.value.indexOf('option1'); let lastIndex = firstIndex + 'option1'.length; input.focus(); input.setSelectionRange(firstIndex, lastIndex); break; case 'wildcard-char': input.value += '.'; break; case 'and': this.conditionHandler('and'); break; case 'or': this.conditionHandler('or'); break; default: break; } this.optionToggleHandler(); } characterIncidenceModifierHandler(elem) { let input = this.tokenInputCheck(this.elements.tokenBuilderContent); input.value += elem.dataset.token; } characterNMSubmitHandler(modalId) { let modal = document.querySelector(`#${modalId}`); let input_n = modal.querySelector('.n-m-input[data-value-type="n"]').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); instance.close(); let tokenInput = this.tokenInputCheck(this.elements.tokenBuilderContent); tokenInput.value += '{' + input + '}'; } conditionHandler(conditionText) { let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true); tokenQueryTemplateClone.querySelector('.token-query-template-content').appendChild(this.elements.tokenBuilderContent.firstElementChild); let notSelectedButton = tokenQueryTemplateClone.querySelector(`[data-condition-pretty-text]:not([data-condition-pretty-text="${conditionText}"])`); let deleteButton = tokenQueryTemplateClone.querySelector(`[data-token-query-content-action="delete"]`); deleteButton.addEventListener('click', (event) => { this.deleteTokenQueryRow(event.target); }); notSelectedButton.parentNode.removeChild(notSelectedButton); this.elements.tokenQuery.appendChild(tokenQueryTemplateClone); // Deleting the options which do not make sense in the context of the condition like "word" AND "word". Also sets selection default. let selectionDefault = "word"; let optionDeleteList = ['empty-token']; if (conditionText === 'and') { switch (this.elements.positionalAttrSelection.value) { case 'english-pos' || 'german-pos': optionDeleteList.push('english-pos', 'german-pos'); break; default: optionDeleteList.push(this.elements.positionalAttrSelection.value); break; } } else { let originalSelectionList = ` <option value="word" selected>word</option> <option value="lemma" >lemma</option> <option value="english-pos">english pos</option> <option value="german-pos">german pos</option> <option value="simple_pos">simple_pos</option> `; this.elements.positionalAttrSelection.innerHTML = originalSelectionList; M.FormSelect.init(this.elements.positionalAttrSelection); } let lastTokenQueryRow = this.elements.tokenQuery.lastElementChild; if(lastTokenQueryRow.querySelector('[data-kind-of-token="word"]') || lastTokenQueryRow.querySelector('[data-kind-of-token="lemma"]')) { this.appendIgnoreCaseCheckbox(lastTokenQueryRow.querySelector('.token-query-template-content'), this.elements.ignoreCaseCheckbox.checked); } this.elements.ignoreCaseCheckbox.checked = false; this.setTokenSelection(selectionDefault, optionDeleteList); } deleteTokenQueryRow(deleteButton) { let deletedRow = deleteButton.closest('.row'); let condition = deletedRow.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText; if (condition === 'and') { let kindOfToken = deletedRow.querySelector('[data-kind-of-token]').dataset.kindOfToken; switch (kindOfToken) { case 'english-pos' || 'german-pos': this.createOptionElementForPosAttrSelection('english-pos'); this.createOptionElementForPosAttrSelection('german-pos'); break; default: this.createOptionElementForPosAttrSelection(kindOfToken); break; } M.FormSelect.init(this.elements.positionalAttrSelection); } deletedRow.remove(); } createOptionElementForPosAttrSelection(kindOfToken) { let option = document.createElement('option'); option.value = kindOfToken; option.text = kindOfToken; this.elements.positionalAttrSelection.appendChild(option); } appendIgnoreCaseCheckbox(parentElement, checked = false) { let ignoreCaseCheckboxClone = document.querySelector('#ignore-case-checkbox-template').content.cloneNode(true); parentElement.appendChild(ignoreCaseCheckboxClone); M.Tooltip.init(parentElement.querySelectorAll('.tooltipped')); if (checked) { parentElement.querySelector('input[type="checkbox"]').checked = true; } } setTokenSelection(selection, optionDeleteList) { optionDeleteList.forEach(option => { if (this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`) !== null) { this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`).remove(); } }); this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], selection); this.preparePositionalAttrModal(); } preparePositionalAttrModal() { let selection = this.elements.positionalAttrSelection.value; if (selection !== 'empty-token') { let selectionTemplate = document.querySelector(`.token-builder-section[data-token-builder-section="${selection}"]`); let selectionTemplateClone = selectionTemplate.content.cloneNode(true); this.elements.tokenBuilderContent.innerHTML = ''; this.elements.tokenBuilderContent.appendChild(selectionTemplateClone); if (this.elements.tokenBuilderContent.querySelector('select') !== null) { let selectElement = this.elements.tokenBuilderContent.querySelector('select'); M.FormSelect.init(selectElement); selectElement.addEventListener('change', () => {this.optionToggleHandler();}); } else { this.elements.tokenBuilderContent.querySelector('input').addEventListener('input', () => {this.optionToggleHandler();}); } } this.optionToggleHandler(); if (selection === 'word' || selection === 'lemma') { this.app.toggleClass(['input-field-options'], 'hide', 'remove'); } else if (selection === 'empty-token'){ this.addTokenToQuery(); } else { this.app.toggleClass(['input-field-options'], 'hide', 'add'); } } tokenInputCheck(elem) { return elem.querySelector('select') !== null ? elem.querySelector('select') : elem.querySelector('input'); } optionToggleHandler() { let input = this.tokenInputCheck(this.elements.tokenBuilderContent); if (input.value === '' && this.elements.editingModusOn === false) { this.app.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add'); } else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) { this.app.toggleClass(['and'], 'disabled', 'add'); this.app.toggleClass(['or'], 'disabled', 'remove'); } else { this.app.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'remove'); } } addTokenToQuery() { let tokenQueryPrettyText = ''; let tokenQueryCQLText = ''; let input; let kindOfToken = this.kindOfTokenCheck(this.elements.positionalAttrSelection.value); // Takes all rows of the token query (if there is a query concatenation). // Adds their contents to tokenQueryPrettyText and tokenQueryCQLText, which will later be expanded with the current input field. let tokenQueryRows = this.elements.tokenQuery.querySelectorAll('.row'); tokenQueryRows.forEach(row => { let ignoreCaseCheckbox = row.querySelector('input[type="checkbox"]'); let c = ignoreCaseCheckbox !== null && ignoreCaseCheckbox.checked ? ' %c' : ''; let tokenQueryRowInput = this.tokenInputCheck(row.querySelector('.token-query-template-content')); let tokenQueryKindOfToken = this.kindOfTokenCheck(tokenQueryRowInput.closest('.input-field').dataset.kindOfToken); let tokenConditionPrettyText = row.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText; let tokenConditionCQLText = row.querySelector('[data-condition-cql-text]').dataset.conditionCqlText; tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `; tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`; }); if (kindOfToken === 'empty-token') { tokenQueryPrettyText += 'empty token'; } else { let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : ''; input = this.tokenInputCheck(this.elements.tokenBuilderContent); tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`; tokenQueryCQLText += `${kindOfToken}="${input.value}"${c}`; } // isTokenQueryInvalid looks if a valid value is passed. If the input fields/dropdowns are empty (isTokenQueryInvalid === true), no token is added. if (this.elements.positionalAttrSelection.value !== 'empty-token' && input.value === '') { this.disableTokenSubmit(); } else { tokenQueryCQLText = `[${tokenQueryCQLText}]`; this.app.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, kindOfToken === 'empty-token' ? false : true); this.elements.positionalAttrModal.close(); } } kindOfTokenCheck(kindOfToken) { return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken; } disableTokenSubmit() { this.elements.tokenSubmitButton.classList.add('red'); this.elements.noValueMessage.classList.remove('hide'); setTimeout(() => { this.elements.tokenSubmitButton.classList.remove('red'); }, 500); setTimeout(() => { this.elements.noValueMessage.classList.add('hide'); }, 3000); } editTokenChipElement(queryElementsContent) { this.elements.positionalAttrModal.open(); queryElementsContent.forEach((queryElement) => { this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr); this.preparePositionalAttrModal(); switch (queryElement.tokenAttr) { case 'word': case 'lemma': this.elements.tokenBuilderContent.querySelector('input').value = queryElement.tokenValue; break; case 'english-pos': // English-pos is selected by default. Then it is checked whether the passed token value occurs in the english-pos selection. If not, the selection is reseted and changed to german-pos. let selection = this.elements.tokenBuilderContent.querySelector('select'); queryElement.tokenAttr = selection.querySelector(`option[value=${queryElement.tokenValue}]`) ? 'english-pos' : 'german-pos'; this.app.resetMaterializeSelection([this.elements.positionalAttrSelection], queryElement.tokenAttr); this.preparePositionalAttrModal(); this.app.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue); break; case 'simple_pos': this.app.resetMaterializeSelection([this.elements.tokenBuilderContent.querySelector('select')], queryElement.tokenValue); default: break; } if (queryElement.ignoreCase) { this.elements.ignoreCaseCheckbox.checked = true; } if (queryElement.condition !== undefined) { this.conditionHandler(queryElement.condition, true); } }); } prepareTokenQueryElementsContent(queryChipElement) { //this regex searches for word or lemma or pos or simple_pos="any string within single or double quotes" followed by one or no ignore case markers, followed by one or no condition characters. let regex = new RegExp('(word|lemma|pos|simple_pos)=(("[^"]+")|(\\\\u0027[^\\\\u0027]+\\\\u0027)) ?(%c)? ?(\\&|\\|)?', 'gm'); let m; let queryElementsContent = []; while ((m = regex.exec(queryChipElement.dataset.query)) !== null) { // this is necessary to avoid infinite loops with zero-width matches if (m.index === regex.lastIndex) { regex.lastIndex++; } let tokenAttr = m[1]; // Passes english-pos by default so that the template is added. In editTokenChipElement it is then checked whether it is english-pos or german-pos. if (tokenAttr === 'pos') { tokenAttr = 'english-pos'; } let tokenValue = m[2].replace(/"|'/g, ''); let ignoreCase = false; let condition = undefined; m.forEach((match) => { if (match === "%c") { ignoreCase = true; } else if (match === "&") { condition = "and"; } else if (match === "|") { condition = "or"; } }); queryElementsContent.push({tokenAttr: tokenAttr, tokenValue: tokenValue, ignoreCase: ignoreCase, condition: condition}); } return queryElementsContent; } }