From 04f2970ccfe35dc0e3d052883f56fb16e9580756 Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Tue, 18 Jun 2024 10:58:20 -0600 Subject: [PATCH] Start moving click handlers on `document.body` Instead of adding listeners to individual elements, the body click routes for better efficiency --- src/js/account/setupListeners.js | 2 - src/js/setupListeners/buttons.js | 70 ++++++++++-------- src/js/setupListeners/details.js | 113 +++++++++++++++--------------- src/js/setupListeners/index.js | 53 ++++++++------ src/js/setupListeners/modals.js | 18 +---- src/js/setupListeners/search.js | 23 +++--- src/js/setupListeners/settings.js | 50 ++++++++----- src/js/setupListeners/words.js | 5 +- 8 files changed, 177 insertions(+), 157 deletions(-) diff --git a/src/js/account/setupListeners.js b/src/js/account/setupListeners.js index c934317..d8fec90 100644 --- a/src/js/account/setupListeners.js +++ b/src/js/account/setupListeners.js @@ -3,7 +3,6 @@ import { setCookie } from "../StackOverflow/cookie"; import { changeDictionary, createNewDictionary } from "./dictionaryManagement"; import { addMessage } from "../utilities"; import { renderForgotPasswordForm } from "./passwordReset"; -import { setupMaximizeButtons } from "../setupListeners/buttons"; export function setupLoginModal(modal) { const closeElements = modal.querySelectorAll('.modal-background, .close-button'); @@ -74,5 +73,4 @@ export function setupMakePublic() { document.execCommand('copy'); addMessage('Copied public link to clipboard', 3000); }); - setupMaximizeButtons(); } diff --git a/src/js/setupListeners/buttons.js b/src/js/setupListeners/buttons.js index 7d2acaf..5796587 100644 --- a/src/js/setupListeners/buttons.js +++ b/src/js/setupListeners/buttons.js @@ -2,41 +2,49 @@ import { renderMaximizedTextbox, renderInfoModal, renderIPATable, renderIPAHelp import helpFile from '../../markdown/help.md'; import termsFile from '../../markdown/terms.md'; import privacyFile from '../../markdown/privacy.md'; -import { setupSearchBar } from './search'; -import { setupSettingsModal } from './modals'; +import { handleSearchClickEvents } from './search'; +import { handleSettingsModalClicks } from './settings'; -export function setupHeaderButtons() { - setupSearchBar(); - setupSettingsModal(); - - document.getElementById('loginCreateAccountButton').addEventListener('click', () => { - import('../account/index.js').then(account => { - account.showLoginForm(); - }); - }); +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +export function handleHeaderButtonClicks(when) { + handleSearchClickEvents(when); + handleSettingsModalClicks(when); + when('#loginCreateAccountButton', () => loadAccountScript('showLoginForm')); } -export function setupIPAButtons() { - const ipaTableButtons = document.getElementsByClassName('ipa-table-button'), - ipaFieldHelpButtons = document.getElementsByClassName('ipa-field-help-button'); - - Array.from(ipaTableButtons).forEach(button => { - button.removeEventListener('click', renderIPATable); - button.addEventListener('click', renderIPATable); - }); - - Array.from(ipaFieldHelpButtons).forEach(button => { - button.removeEventListener('click', renderIPAHelp); - button.addEventListener('click', renderIPAHelp); - }); +// TODO: Set this up correctly +function loadAccountScript(accountScriptMethod) { + let script = document.getElementById('accountScript'); + if (script) { + Account[accountScriptMethod](); + } else { + script = document.createElement('script'); + document.body.appendChild(script); + script.onload = () => { + Account[accountScriptMethod](); + } + script.src = './account.js'; + } } -export function setupMaximizeButtons() { - const maximizeButtons = document.getElementsByClassName('maximize-button'); - Array.from(maximizeButtons).forEach(button => { - button.removeEventListener('click', renderMaximizedTextbox); - button.addEventListener('click', renderMaximizedTextbox); - }); +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +export function handleIPAButtonClicks(when) { + when('.ipa-table-button', renderIPATable); + when('.ipa-field-help-button', renderIPAHelp); +} + +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +export function handleMaximizeButtonClicks(when) { + when('.maximize-button', renderMaximizedTextbox); } export function setupInfoButtons() { @@ -49,4 +57,4 @@ export function setupInfoButtons() { document.getElementById('privacyInfoButton').addEventListener('click', () => { renderInfoModal(privacyFile); }); -} \ No newline at end of file +} diff --git a/src/js/setupListeners/details.js b/src/js/setupListeners/details.js index be9e615..fe2917c 100644 --- a/src/js/setupListeners/details.js +++ b/src/js/setupListeners/details.js @@ -1,68 +1,65 @@ import { showSection, hideDetailsPanel } from '../displayToggles'; import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords, confirmDeleteDictionary } from '../dictionaryManagement'; -import { setupMaximizeButtons } from './buttons'; -export function setupDetailsTabs() { - const tabs = document.querySelectorAll('#detailsSection nav li'); - tabs.forEach(tab => { - tab.addEventListener('click', () => { - const section = tab.innerText.toLowerCase(); - if (section === 'edit') { - openEditModal(); - } else { - const isActive = tab.classList.contains('active'); - tabs.forEach(t => t.classList.remove('active')); - if (isActive) { - hideDetailsPanel(); - } else { - tab.classList.add('active'); - showSection(section); - } - } - }); - }); - setupEditFormTabs(); - setupEditFormInteractions(); - setupEditFormButtons(); +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +export function handleDetailClicks(when) { + // Details Tabs + when('#detailsSection nav li', handleClickTab); + // Edit Form Tabs + when('#editModal nav li', handleClickEditFormTab); + // Edit Form Buttons + when('#editSave', saveEditModal); + when('#editSaveAndClose', saveAndCloseEditModal); + when('#importDictionaryFile', importDictionary); + when('#importWordsCSV', importWords); + when('#exportDictionaryButton', exportDictionary); + when('#exportWordsButton', exportWords); + when('#deleteDictionaryButton', confirmDeleteDictionary); } -function setupEditFormTabs() { - const tabs = document.querySelectorAll('#editModal nav li'); - tabs.forEach(tab => { - tab.addEventListener('click', () => { - tabs.forEach(t => { - t.classList.remove('active'); - document.getElementById('edit' + t.innerText + 'Tab').style.display = 'none'; - }); - tab.classList.add('active'); - const tabSection = document.getElementById('edit' + tab.innerText + 'Tab'); - tabSection.style.display = ''; - tabSection.scrollTop = 0; - }); - }); -} - -function setupEditFormInteractions() { - const preventDuplicatesBox = document.getElementById('editPreventDuplicates'); - preventDuplicatesBox.addEventListener('change', () => { - const caseSensitiveBox = document.getElementById('editCaseSensitive'); - if (preventDuplicatesBox.checked) { - caseSensitiveBox.disabled = false; +function handleClickTab(tabElement) { + const section = tabElement.innerText.toLowerCase(); + if (section === 'edit') { + openEditModal(); + } else { + const isActive = tabElement.classList.contains('active'); + document.querySelectorAll('#detailsSection nav li') + .forEach(t => t.classList.remove('active')); + if (isActive) { + hideDetailsPanel(); } else { - caseSensitiveBox.disabled = true; - caseSensitiveBox.checked = false; + tabElement.classList.add('active'); + showSection(section); } - }); + } } -function setupEditFormButtons() { - document.getElementById('editSave').addEventListener('click', saveEditModal); - document.getElementById('editSaveAndClose').addEventListener('click', saveAndCloseEditModal); - document.getElementById('importDictionaryFile').addEventListener('change', importDictionary); - document.getElementById('importWordsCSV').addEventListener('change', importWords); - document.getElementById('exportDictionaryButton').addEventListener('click', exportDictionary); - document.getElementById('exportWordsButton').addEventListener('click', exportWords); - document.getElementById('deleteDictionaryButton').addEventListener('click', confirmDeleteDictionary); +function handleClickEditFormTab(tabElement) { + document.querySelectorAll('#editModal nav li').forEach(t => { + t.classList.remove('active'); + document.getElementById('edit' + t.innerText + 'Tab').style.display = 'none'; + }); + tabElement.classList.add('active'); + const tabSection = document.getElementById('edit' + tabElement.innerText + 'Tab'); + tabSection.style.display = ''; + tabSection.scrollTop = 0; +} - setupMaximizeButtons(); -} \ No newline at end of file +export function setupEditFormInteractions() { + const preventDuplicatesBox = document.getElementById('editPreventDuplicates'); + preventDuplicatesBox.removeEventListener('change', handlePreventDuplicatesBoxChange); + preventDuplicatesBox.addEventListener('change', handlePreventDuplicatesBoxChange); +} + +function handlePreventDuplicatesBoxChange(event) { + const caseSensitiveBox = document.getElementById('editCaseSensitive'); + if (event.target.checked) { + caseSensitiveBox.disabled = false; + } else { + caseSensitiveBox.disabled = true; + caseSensitiveBox.checked = false; + } +} diff --git a/src/js/setupListeners/index.js b/src/js/setupListeners/index.js index 6121564..c734641 100644 --- a/src/js/setupListeners/index.js +++ b/src/js/setupListeners/index.js @@ -1,17 +1,19 @@ import { usePhondueDigraphs } from '../KeyboardFire/phondue/ipaField'; import { enableHotKeys } from '../hotkeys'; -import { dismiss, isDismissed } from '../announcements'; -import { fadeOutElement } from '../utilities'; -import { setupDetailsTabs } from './details'; +import { dismiss } from '../announcements'; +import { handleDetailClicks, setupEditFormInteractions } from './details'; import { setupWordForm, setupMobileWordFormButton } from './words'; -import { setupIPAButtons, setupHeaderButtons, setupInfoButtons } from './buttons'; -import { setupTemplateForm, setupTemplateSelectOptions } from './settings'; +import { setupInfoButtons, handleIPAButtonClicks, handleMaximizeButtonClicks } from './buttons'; +import { handleTemplateFormClicks, setupTemplateChangeEvents, setupTemplateSelectOptions } from './settings'; +import { setupSearchBarEvents } from './search'; export default function setupListeners() { - setupAnnouncements(); - setupDetailsTabs(); - setupTemplateForm(); - setupHeaderButtons(); + document.body.addEventListener('click', handleClickEvents); + + setupEditFormInteractions(); + setupTemplateChangeEvents(); + setupSearchBarEvents(); + setupWordForm(); setupMobileWordFormButton(); setupInfoButtons(); @@ -21,25 +23,36 @@ export default function setupListeners() { } } -function setupAnnouncements() { - const announcements = document.querySelectorAll('.announcement'); - Array.from(announcements).forEach(announcement => { - if (announcement.id && isDismissed(announcement.id)) { - fadeOutElement(announcement); - } else { - announcement.querySelector('.close-button').addEventListener('click', () => dismiss(announcement)); +function handleClickEvents(event) { + const when = (selector, cb) => { + if (event.target.matches(selector)) { + cb(event.target); } + }; + + handleClickAccouncementClose(when); + handleDetailClicks(when); + handleTemplateFormClicks(when); + handleIPAButtonClicks(when); + handleMaximizeButtonClicks(when); +} + +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +function handleClickAccouncementClose(when) { + when('.announcement .close-button', closeElement => { + dismiss(closeElement.parentElement); }); } -export function setupIPAFields() { +export function setupIPAFields(parent) { if (window.settings.useIPAPronunciationField) { - const ipaFields = document.getElementsByClassName('ipa-field'); + const ipaFields = (parent ?? document).querySelectorAll('.ipa-field'); Array.from(ipaFields).forEach(field => { field.removeEventListener('keypress', usePhondueDigraphs); field.addEventListener('keypress', usePhondueDigraphs); }); } - - setupIPAButtons(); } diff --git a/src/js/setupListeners/modals.js b/src/js/setupListeners/modals.js index 6738cc5..490260b 100644 --- a/src/js/setupListeners/modals.js +++ b/src/js/setupListeners/modals.js @@ -1,20 +1,4 @@ import { insertAtCursor, getInputSelection, setSelectionRange } from '../StackOverflow/inputCursorManagement'; -import { - openSettingsModal, - saveSettingsModal, - saveAndCloseSettingsModal, - createTemplate, - saveTemplate -} from '../settings'; - -export function setupSettingsModal() { - document.getElementById('createTemplateButton').addEventListener('click', createTemplate); - document.getElementById('saveTemplateButton').addEventListener('click', saveTemplate); - - document.getElementById('settingsButton').addEventListener('click', openSettingsModal); - document.getElementById('settingsSave').addEventListener('click', saveSettingsModal); - document.getElementById('settingsSaveAndClose').addEventListener('click', saveAndCloseSettingsModal); -} export function setupIPATable(modal, textBox) { const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'), @@ -77,4 +61,4 @@ export function setupInfoModal(modal) { modal.parentElement.removeChild(modal); }); }); -} \ No newline at end of file +} diff --git a/src/js/setupListeners/search.js b/src/js/setupListeners/search.js index 6cfd43f..111ef80 100644 --- a/src/js/setupListeners/search.js +++ b/src/js/setupListeners/search.js @@ -1,9 +1,20 @@ import { renderWords } from '../render/words'; import { showSearchModal, clearSearchText, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from '../search'; -export function setupSearchBar() { +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +export function handleSearchClickEvents(when) { + when('#clearSearchButton', clearSearchText); + when('#openSearchModal', showSearchModal); + + when('#checkAllFilters', checkAllPartsOfSpeechFilters); + when('#uncheckAllFilters', uncheckAllPartsOfSpeechFilters); +} + +export function setupSearchBarEvents() { const searchBox = document.getElementById('searchBox'), - clearSearchButton = document.getElementById('clearSearchButton'), openSearchModal = document.getElementById('openSearchModal'), searchIgnoreDiacritics = document.getElementById('searchIgnoreDiacritics'), searchExactWords = document.getElementById('searchExactWords'), @@ -14,8 +25,6 @@ export function setupSearchBar() { searchBox.addEventListener('input', event => { openSearchModal.value = event.target.value; }); - clearSearchButton.addEventListener('click', clearSearchText); - openSearchModal.addEventListener('click', showSearchModal); const toggleDetailsCheck = function() { if (searchExactWords.checked) { @@ -47,8 +56,4 @@ export function setupSearchFilters() { filter.removeEventListener('change', renderWords); filter.addEventListener('change', renderWords); }); - document.getElementById('checkAllFilters').removeEventListener('click', checkAllPartsOfSpeechFilters); - document.getElementById('checkAllFilters').addEventListener('click', checkAllPartsOfSpeechFilters); - document.getElementById('uncheckAllFilters').removeEventListener('click', uncheckAllPartsOfSpeechFilters); - document.getElementById('uncheckAllFilters').addEventListener('click', uncheckAllPartsOfSpeechFilters); -} \ No newline at end of file +} diff --git a/src/js/setupListeners/settings.js b/src/js/setupListeners/settings.js index f61b76b..d4adf5e 100644 --- a/src/js/setupListeners/settings.js +++ b/src/js/setupListeners/settings.js @@ -1,31 +1,49 @@ -import { createTemplate, saveTemplate, editSavedTemplate, deleteSelectedTemplate } from "../settings"; +import { + createTemplate, + saveTemplate, + editSavedTemplate, + deleteSelectedTemplate, + openSettingsModal, + saveSettingsModal, + saveAndCloseSettingsModal +} from "../settings"; -export function setupTemplateForm() { - document.getElementById('createTemplateButton').addEventListener('click', createTemplate); - document.getElementById('saveTemplateButton').addEventListener('click', saveTemplate); - document.getElementById('deleteTemplateButton').addEventListener('click', deleteSelectedTemplate); - setupSavedTemplatesSelect(); +/** + * Identify selector strings and handlers + * @param {Function} when Passed from setupListeners, which listens to clicks on document.body + */ +export function handleSettingsModalClicks(when) { + when('#createTemplateButton', createTemplate); + when('#saveTemplateButton', saveTemplate); + when('#deleteTemplateButton', deleteSelectedTemplate); + + when('#settingsButton', openSettingsModal); + when('#settingsSave', saveSettingsModal); + when('#settingsSaveAndClose', saveAndCloseSettingsModal); } -export function setupSavedTemplatesSelect() { +export function setupTemplateChangeEvents() { const savedTemplatesSelect = document.getElementById('savedDetailsTemplates'); - savedTemplatesSelect.removeEventListener('change', editSavedTemplate); savedTemplatesSelect.addEventListener('change', editSavedTemplate); + + setupTemplateSelectOptions(); } export function setupTemplateSelectOptions() { - const fillDetailsWithTemplate = function (e) { - if (e.target.value !== '') { - const template = window.settings.templates[parseInt(e.target.value)]; - let detailsId = 'wordDetails' + e.target.id.replace('templateSelect', ''); - document.getElementById(detailsId).value = template.template; - } - } Array.from(document.getElementsByClassName('template-select')).forEach(select => { if (select.id !== 'savedDetailsTemplates') { select.removeEventListener('change', fillDetailsWithTemplate); select.addEventListener('change', fillDetailsWithTemplate); } }); -} \ No newline at end of file +} + +function fillDetailsWithTemplate (event) { + const { target } = event; + if (target.value !== '') { + const template = window.settings.templates[parseInt(target.value)]; + let detailsId = 'wordDetails' + target.id.replace('templateSelect', ''); + document.getElementById(detailsId).value = template.template; + } +} diff --git a/src/js/setupListeners/words.js b/src/js/setupListeners/words.js index 69757f6..ace57ac 100644 --- a/src/js/setupListeners/words.js +++ b/src/js/setupListeners/words.js @@ -1,7 +1,6 @@ import { renderEditForm } from '../render/words'; import { confirmEditWord, cancelEditWord, confirmDeleteWord, expandAdvancedForm, submitWordForm } from '../wordManagement'; // import { goToNextPage, goToPreviousPage, goToPage } from '../pagination'; -import { setupMaximizeButtons } from './buttons'; import { setupIPAFields } from '.'; export function setupWordForm() { @@ -17,7 +16,6 @@ export function setupWordForm() { addWordButton.addEventListener('click', submitWordForm); setupIPAFields(); - setupMaximizeButtons(); } export function setupWordOptionButtons() { @@ -80,7 +78,6 @@ export function setupWordEditFormButtons() { }); setupIPAFields(); - setupMaximizeButtons(); } export function setupMobileWordFormButton() { @@ -116,4 +113,4 @@ export function setupMobileWordFormButton() { // pageSelector.removeEventListener('change', goToPage); // pageSelector.addEventListener('change', goToPage); // }); -// } \ No newline at end of file +// }