diff --git a/src/main/resources/static/css/dark/templates.css b/src/main/resources/static/css/dark/templates.css index 78808d9083e468851d69e7f3909c1995550082e3..2dfa2860d7a48c0e9d325040c34e38adda159829 100644 --- a/src/main/resources/static/css/dark/templates.css +++ b/src/main/resources/static/css/dark/templates.css @@ -11,4 +11,8 @@ right: 15px; top: 8px; font-weight: bold; +} + +.template-selected { + background-color: #888888; } \ No newline at end of file diff --git a/src/main/resources/static/css/templates.css b/src/main/resources/static/css/templates.css index 0ac6c5fb53392acea833fbc04d9970aa788c231a..2d1c2225e43413cde60fe6ff915adb8073185411 100644 --- a/src/main/resources/static/css/templates.css +++ b/src/main/resources/static/css/templates.css @@ -13,3 +13,6 @@ font-weight: bold; } +.template-selected { + background-color: rgb(238, 238, 238); +} diff --git a/src/main/resources/static/js/hotkeys.js b/src/main/resources/static/js/hotkeys.js index caf9d11cbc8ddebc23bbc9905bfb0c2fb499e311..8b28bf96ad69bcb0b3381742cb7006d32470b1b7 100644 --- a/src/main/resources/static/js/hotkeys.js +++ b/src/main/resources/static/js/hotkeys.js @@ -66,16 +66,21 @@ if(saveTransactionOrTemplateButton !== null) function areHotKeysEnabled() { - return !isSearchFocused() && !isCategorySelectFocused(); + return !isSearchFocused() && !isCategorySelectFocused() && !isTemplateSearchFocused(); } - function isSearchFocused() { let searchElement = document.getElementById('search'); return document.activeElement === searchElement; } +function isTemplateSearchFocused() +{ + let templateSearchElement = document.getElementById('searchTemplate'); + return document.activeElement === templateSearchElement; +} + function isCategorySelectFocused() { let activeElement = document.activeElement; diff --git a/src/main/resources/static/js/templates.js b/src/main/resources/static/js/templates.js index eae1407d3bffa4f02496c35f954d06b4c15ab7f8..602a682cd8c125f4d06901683b427c0927b48814 100644 --- a/src/main/resources/static/js/templates.js +++ b/src/main/resources/static/js/templates.js @@ -38,8 +38,12 @@ $(document).ready(function() { document.getElementById('searchTemplate').focus(); } + + enableHotKeys(); }); +let selectedTemplateName = null; + function handleIncludeAccountCheckbox(checkboxID, selectID) { document.getElementById(checkboxID).addEventListener('change', (event) => @@ -90,9 +94,124 @@ function searchTemplates(searchText) if(numberOfVisibleItems === 0) { collapsible.classList.add('hidden'); + + // hide all item selections + let templateItems = document.getElementsByClassName('template-item'); + for(let i = 0; i < templateItems.length; i++) + { + toggleItemSelection(templateItems[i], false); + } + selectedTemplateName = null; } else { collapsible.classList.remove('hidden'); } } + +function enableHotKeys() +{ + Mousetrap.bind('up', function() + { + handleKeyUpOrDown(true); + }); + + Mousetrap.bind('down', function() + { + handleKeyUpOrDown(false); + }); +} + +function handleKeyUpOrDown(isUp) +{ + let templateItems = document.querySelectorAll('.template-item:not(.hidden)'); + for(let i = 0; i < templateItems.length; i++) + { + toggleItemSelection(templateItems[i], false); + } + + if(templateItems.length === 0) + { + selectedTemplateName = null; + return; + } + + let previousIndex = getIndexOfTemplateName(templateItems, selectedTemplateName); + let noItemSelected = selectedTemplateName === null; + let previousItemNoLongerInList = previousIndex === null; + + if(noItemSelected || previousItemNoLongerInList) + { + // select the first item + selectItem(templateItems, 0); + } + else + { + // select next item + if(isUp) + { + selectNextItemOnUp(templateItems, previousIndex); + } + else + { + selectNextItemOnDown(templateItems, previousIndex); + } + } +} + +function selectItem(templateItems, index) +{ + toggleItemSelection(templateItems[index], true); + selectedTemplateName = getTemplateName(templateItems[index]); + document.getElementById('searchTemplate').focus(); +} + +function toggleItemSelection(templateItem, isSelected) +{ + templateItem.getElementsByClassName('collapsible-header')[0].classList.toggle('template-selected', isSelected); +} + +function getTemplateName(templateItem) +{ + return templateItem.getElementsByClassName('template-header-name')[0]; +} + +function getIndexOfTemplateName(templateItems, templateName) +{ + for(let i = 0; i < templateItems.length; i++) + { + let currentTemplateName = getTemplateName(templateItems[i]); + if(currentTemplateName === templateName) + { + return i; + } + } + + return null; +} + +function selectNextItemOnDown(templateItems, previousIndex) +{ + let isLastItemSelected = previousIndex + 1 === templateItems.length; + if(isLastItemSelected) + { + selectItem(templateItems, 0); + } + else + { + selectItem(templateItems, previousIndex + 1); + } +} + +function selectNextItemOnUp(templateItems, previousIndex) +{ + let isFirstItemSelected = previousIndex === 0; + if(isFirstItemSelected) + { + selectItem(templateItems, templateItems.length - 1); + } + else + { + selectItem(templateItems, previousIndex - 1); + } +} \ No newline at end of file diff --git a/src/main/resources/templates/templates/templates.ftl b/src/main/resources/templates/templates/templates.ftl index 4e86b2114531d965e4f64afa94c6d0b9790f1979..6d87982fab93fdc237eb56f9cc4cbd951c531cae 100644 --- a/src/main/resources/templates/templates/templates.ftl +++ b/src/main/resources/templates/templates/templates.ftl @@ -22,7 +22,7 @@ <div class="row"> <div class="input-field col s12 m12 l8 offset-l2"> <i class="material-icons prefix">search</i> - <input id="searchTemplate" type="text"> + <input id="searchTemplate" type="text" class="mousetrap"> <label for="searchTemplate">${locale.getString("search")}</label> </div> </div>