From d27fcca6cefb40d30c5434e646b84d8d45e86ecb Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sat, 21 Nov 2020 18:38:38 +0100
Subject: [PATCH] #533 - select template items on up or down key

---
 .../resources/static/css/dark/templates.css   |   4 +
 src/main/resources/static/css/templates.css   |   3 +
 src/main/resources/static/js/hotkeys.js       |   9 +-
 src/main/resources/static/js/templates.js     | 119 ++++++++++++++++++
 .../templates/templates/templates.ftl         |   2 +-
 5 files changed, 134 insertions(+), 3 deletions(-)

diff --git a/src/main/resources/static/css/dark/templates.css b/src/main/resources/static/css/dark/templates.css
index 78808d908..2dfa2860d 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 0ac6c5fb5..2d1c2225e 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 caf9d11cb..8b28bf96a 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 eae1407d3..602a682cd 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 4e86b2114..6d87982fa 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>
-- 
GitLab