From 6fb47427c7a29d76fb86d61c8da9d59a57392c95 Mon Sep 17 00:00:00 2001
From: Clement Ho <ClemMakesApps@gmail.com>
Date: Wed, 9 Nov 2016 17:07:30 -0600
Subject: [PATCH] Refactor tokenizer

---
 .../filtered_search_manager.js.es6            | 120 ++++--------------
 .../filtered_search_tokenizer.es6             |  90 +++++++++++++
 2 files changed, 115 insertions(+), 95 deletions(-)
 create mode 100644 app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6

diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
index 1b58fc01608..58c64ea078d 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
@@ -18,13 +18,21 @@
     param: 'name[]',
   }];
 
+  function clearSearch(event) {
+    event.stopPropagation();
+    event.preventDefault();
+
+    document.querySelector('.filtered-search').value = '';
+    document.querySelector('.clear-search').classList.add('hidden');
+  }
+
   function toggleClearSearchButton(event) {
-    const clearSearch = document.querySelector('.clear-search');
+    const clearSearchButton = document.querySelector('.clear-search');
 
     if (event.target.value) {
-      clearSearch.classList.remove('hidden');
+      clearSearchButton.classList.remove('hidden');
     } else {
-      clearSearch.classList.add('hidden');
+      clearSearchButton.classList.add('hidden');
     }
   }
 
@@ -74,105 +82,24 @@
 
   class FilteredSearchManager {
     constructor() {
+      this.tokenizer = new gl.FilteredSearchTokenizer(validTokenKeys);
       this.bindEvents();
       loadSearchParamsFromURL();
-      this.clearTokens();
     }
 
     bindEvents() {
-      const input = document.querySelector('.filtered-search');
-      const clearSearch = document.querySelector('.clear-search');
-
-      input.addEventListener('input', this.tokenize.bind(this));
-      input.addEventListener('input', toggleClearSearchButton);
-      input.addEventListener('keydown', this.checkForEnter.bind(this));
+      const filteredSearchInput = document.querySelector('.filtered-search');
 
-      clearSearch.addEventListener('click', this.clearSearch.bind(this));
-    }
-
-    clearSearch(event) {
-      event.stopPropagation();
-      event.preventDefault();
-
-      this.clearTokens();
-      document.querySelector('.filtered-search').value = '';
-      document.querySelector('.clear-search').classList.add('hidden');
-    }
+      filteredSearchInput.addEventListener('input', this.processInput.bind(this));
+      filteredSearchInput.addEventListener('input', toggleClearSearchButton);
+      filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this));
 
-    clearTokens() {
-      this.tokens = [];
-      this.searchToken = '';
+      document.querySelector('.clear-search').addEventListener('click', clearSearch);
     }
 
-    tokenize(event) {
-      // Re-calculate tokens
-      this.clearTokens();
-
+    processInput(event) {
       const input = event.target.value;
-      const inputs = input.split(' ');
-      let searchTerms = '';
-      let lastQuotation = '';
-      let incompleteToken = false;
-
-      const addSearchTerm = function addSearchTerm(term) {
-        // Add space for next term
-        searchTerms += `${term} `;
-      };
-
-      inputs.forEach((i) => {
-        if (incompleteToken) {
-          const prevToken = this.tokens[this.tokens.length - 1];
-          prevToken.value += ` ${i}`;
-
-          // Remove last quotation
-          const lastQuotationRegex = new RegExp(lastQuotation, 'g');
-          prevToken.value = prevToken.value.replace(lastQuotationRegex, '');
-          this.tokens[this.tokens.length - 1] = prevToken;
-
-          // Check to see if this quotation completes the token value
-          if (i.indexOf(lastQuotation)) {
-            incompleteToken = !incompleteToken;
-          }
-
-          return;
-        }
-
-        const colonIndex = i.indexOf(':');
-
-        if (colonIndex !== -1) {
-          const tokenKey = i.slice(0, colonIndex).toLowerCase();
-          const tokenValue = i.slice(colonIndex + 1);
-          const match = validTokenKeys.find(v => v.key === tokenKey);
-
-          if (tokenValue.indexOf('"') !== -1) {
-            lastQuotation = '"';
-            incompleteToken = true;
-          } else if (tokenValue.indexOf('\'') !== -1) {
-            lastQuotation = '\'';
-            incompleteToken = true;
-          }
-
-          if (match && tokenValue.length > 0) {
-            this.tokens.push({
-              key: match.key,
-              value: tokenValue,
-            });
-          } else {
-            addSearchTerm(i);
-          }
-        } else {
-          addSearchTerm(i);
-        }
-      }, this);
-
-      this.searchToken = searchTerms.trim();
-      this.printTokens();
-    }
-
-    printTokens() {
-      console.log('tokens:');
-      this.tokens.forEach(token => console.log(token));
-      console.log(`search: ${this.searchToken}`);
+      this.tokenizer.processTokens(input);
     }
 
     checkForEnter(event) {
@@ -193,6 +120,9 @@
       const defaultState = 'opened';
       let currentState = defaultState;
 
+      const tokens = this.tokenizer.getTokens();
+      const searchToken = this.tokenizer.getSearchToken();
+
       if (stateIndex !== -1) {
         const remaining = currentPath.slice(stateIndex + 6);
         const separatorIndex = remaining.indexOf('&');
@@ -201,13 +131,13 @@
       }
 
       path += `&state=${currentState}`;
-      this.tokens.forEach((token) => {
+      tokens.forEach((token) => {
         const param = validTokenKeys.find(t => t.key === token.key).param;
         path += `&${token.key}_${param}=${encodeURIComponent(token.value)}`;
       });
 
-      if (this.searchToken) {
-        path += `&search=${encodeURIComponent(this.searchToken)}`;
+      if (searchToken) {
+        path += `&search=${encodeURIComponent(searchToken)}`;
       }
 
       window.location = path;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6
new file mode 100644
index 00000000000..f6cc1b8860d
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6
@@ -0,0 +1,90 @@
+/* eslint-disable no-param-reassign */
+((global) => {
+  class FilteredSearchTokenizer {
+    constructor(validTokenKeys) {
+      this.validTokenKeys = validTokenKeys;
+      this.resetTokens();
+    }
+
+    getTokens() {
+      return this.tokens;
+    }
+
+    getSearchToken() {
+      return this.searchToken;
+    }
+
+    resetTokens() {
+      this.tokens = [];
+      this.searchToken = '';
+    }
+
+    printTokens() {
+      console.log('tokens:');
+      this.tokens.forEach(token => console.log(token));
+      console.log(`search: ${this.searchToken}`);
+    }
+
+    processTokens(input) {
+      // Re-calculate tokens
+      this.resetTokens();
+
+      const inputs = input.split(' ');
+      let searchTerms = '';
+      let lastQuotation = '';
+      let incompleteToken = false;
+
+      inputs.forEach((i) => {
+        if (incompleteToken) {
+          const prevToken = this.tokens[this.tokens.length - 1];
+          prevToken.value += ` ${i}`;
+
+          // Remove last quotation
+          const lastQuotationRegex = new RegExp(lastQuotation, 'g');
+          prevToken.value = prevToken.value.replace(lastQuotationRegex, '');
+          this.tokens[this.tokens.length - 1] = prevToken;
+
+          // Check to see if this quotation completes the token value
+          if (i.indexOf(lastQuotation)) {
+            incompleteToken = !incompleteToken;
+          }
+
+          return;
+        }
+
+        const colonIndex = i.indexOf(':');
+
+        if (colonIndex !== -1) {
+          const tokenKey = i.slice(0, colonIndex).toLowerCase();
+          const tokenValue = i.slice(colonIndex + 1);
+          const match = this.validTokenKeys.find(v => v.key === tokenKey);
+
+          if (tokenValue.indexOf('"') !== -1) {
+            lastQuotation = '"';
+            incompleteToken = true;
+          } else if (tokenValue.indexOf('\'') !== -1) {
+            lastQuotation = '\'';
+            incompleteToken = true;
+          }
+
+          if (match && tokenValue.length > 0) {
+            this.tokens.push({
+              key: match.key,
+              value: tokenValue,
+            });
+
+            return;
+          }
+        }
+
+        // Add space for next term
+        searchTerms += `${i} `;
+      }, this);
+
+      this.searchToken = searchTerms.trim();
+      this.printTokens();
+    }
+  }
+
+  global.FilteredSearchTokenizer = FilteredSearchTokenizer;
+})(window.gl || (window.gl = {}));
-- 
2.30.9