From 68ee88bce029e2ac806ffab6a8d76c7f62ed4e5a Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 8 Apr 2018 08:44:28 +0200 Subject: [PATCH] rewrite autocomplete in vue.js --- .../lang/QueryCompletionPdbLangParser.java | 6 +- .../java/org/lucares/pdbui/PdbController.java | 5 - .../main/resources/resources/css/design.css | 20 +++ pdb-ui/src/main/resources/resources/js/ui.js | 154 +++++++++++++++++- 4 files changed, 170 insertions(+), 15 deletions(-) diff --git a/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java b/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java index e14d8c8..8c4e3ce 100644 --- a/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java +++ b/data-store/src/main/java/org/lucares/pdb/datastore/lang/QueryCompletionPdbLangParser.java @@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory; public class QueryCompletionPdbLangParser extends PdbLangParser { - private static final Logger LOGGER = LoggerFactory.getLogger(QueryCompletionPdbLangParser.class); + private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.autocomplete"); public class Listener implements PdbLangListener, ANTLRErrorListener { @@ -71,7 +71,7 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { return new Proposal(p, count > 0); }).forEach(proposals::add); - LOGGER.trace("proposals for property value {} took {} ms", propertyValuePrefix, + METRICS_LOGGER.debug("proposals for property value {} took {} ms", propertyValuePrefix, (System.nanoTime() - startTime) / 1_000_000.0); } else if (_ctx instanceof IdentifierExpressionContext) { final long startTime = System.nanoTime(); @@ -83,7 +83,7 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { addProposalsForKeys(propertyKeyPrefix, newQueryPattern.toString()); - LOGGER.trace("proposals for property key {} took {} ms", propertyKeyPrefix, + METRICS_LOGGER.debug("proposals for property key {} took {} ms", propertyKeyPrefix, (System.nanoTime() - startTime) / 1_000_000.0); } } diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java b/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java index 89b8f24..151a400 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java @@ -1,7 +1,6 @@ package org.lucares.pdbui; import java.text.Collator; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -47,9 +46,6 @@ public class PdbController implements HardcodedValues { private static final Logger LOGGER = LoggerFactory.getLogger(PdbController.class); - private static final DateTimeFormatter DATE_FORMAT_BEGIN = DateTimeFormatter.ofPattern("yyyy-MM-dd 00:00:00"); - private static final DateTimeFormatter DATE_FORMAT_END = DateTimeFormatter.ofPattern("yyyy-MM-dd 23:59:59"); - private final Plotter plotter; private final PerformanceDb db; @@ -111,7 +107,6 @@ public class PdbController implements HardcodedValues { @RequestMapping(path = "/autocomplete", // method = RequestMethod.GET, // - consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, // produces = MediaType.APPLICATION_JSON_UTF8_VALUE // ) @ResponseBody diff --git a/pdb-ui/src/main/resources/resources/css/design.css b/pdb-ui/src/main/resources/resources/css/design.css index 026d62e..ce374e1 100644 --- a/pdb-ui/src/main/resources/resources/css/design.css +++ b/pdb-ui/src/main/resources/resources/css/design.css @@ -84,6 +84,26 @@ body { padding: 2px; } +#proposals { + position: fixed; + background: white; + width: 100%; + padding: 2px; + max-height: 200px; + z-index: 1; + overflow-y: scroll; + box-sizing: border-box; +} + +#proposals div { + padding: 2px; + cursor: default; +} + +#proposals div:hover, #proposals .highlighted { + background: lightgrey; +} + #search-limit-value { width: 4em; } diff --git a/pdb-ui/src/main/resources/resources/js/ui.js b/pdb-ui/src/main/resources/resources/js/ui.js index f4b26dd..93d276b 100644 --- a/pdb-ui/src/main/resources/resources/js/ui.js +++ b/pdb-ui/src/main/resources/resources/js/ui.js @@ -2,6 +2,149 @@ window.onload=function(){ +Vue.config.keyCodes.arrowUp = 38; +Vue.config.keyCodes.arrowDown = 40; + +Vue.component('search-bar-query', { + props: ['searchBar'], + autocompleteTimeout: null, + requestNumber: 1, + created: function() { + this.requestNumber= 1; + document.addEventListener("click", function(event){ + const isSearchInput = event.path.map((e) => e.id == "search-input-wrapper") + .reduce((accumulator, currentValue) => accumulator || currentValue); + if(!isSearchInput){ + data.searchBar.proposals = []; + data.searchBar.showProposals = false; + } + }); + + }, + data: function() { + return { + showProposals: false, + highlightedProposal: -1, + }; + }, + methods: { + onChange: function (event) { + if (event.key == 'ArrowDown' || event.key == 'ArrowUp' || event.key == 'Enter' || event.key == 'Escape'){ + return; + } + //console.log(event.key); + + var vm = this; + if(this.autocompleteTimeout) { + window.clearTimeout(this.autocompleteTimeout); + event.preventDefault(); + } + + vm.showProposals = false; + + var expectedRequestNumber = ++vm.requestNumber; + this.autocompleteTimeout = window.setTimeout(function() { + var caretIndex = document.getElementById('search-input').selectionStart + 1; + var request='autocomplete?caretIndex=' + caretIndex + '&query='+encodeURIComponent(data.searchBar.query); + + var successCallback = function(result) { + if (expectedRequestNumber == vm.requestNumber) { + vm.highlightedProposal = -1; + vm.showProposals = true; + + var index = 0; + var proposals = result.proposals; + proposals.forEach(function(p) { + p.id = "proposal_"+index + p.index = index++; + p.highlighted=false; + }); + data.searchBar.proposals = proposals; + } else { + console.log("ignoring stale response "+ expectedRequestNumber +" != "+ vm.requestNumber); + } + }; + + var errorCallback = function(e) { + console.log("FAILED: " + JSON.parse(e.responseText).message); + vm.showProposals=false; + }; + + getJson(request, {}, successCallback, errorCallback); + }, + 300); + }, + selectOrSubmit: function(event) { + if (this.highlightedProposal >= 0){ + var proposal = data.searchBar.proposals[this.highlightedProposal]; + data.searchBar.query = proposal.proposedQuery; + this.showProposals = false; + event.preventDefault(); + } + }, + selectUpDown: function(event) { + if (event.key == 'ArrowDown'){ + this.highlightedProposal = (this.highlightedProposal+1) % data.searchBar.proposals.length; + } else if (event.key == 'ArrowUp') { + this.highlightedProposal = (this.highlightedProposal-1) % data.searchBar.proposals.length; + this.highlightedProposal = this.highlightedProposal < 0 ? -1 : this.highlightedProposal; + } + }, + + noop: function(event){}, + }, + template: ` +
+ +
+ +
+
` +}); + +Vue.component('proposal-item', { + props: ['proposalItem', 'highlightedProposal'], + computed: { + isHighlighted: function() { + return this.highlightedProposal == this.proposalItem.index; + } + }, + methods: { + selectProposal: function(event) { + var proposal = data.searchBar.proposals[this.proposalItem.index]; + data.searchBar.query = proposal.proposedQuery; + data.searchBar.showProposals = false; + document.getElementById('search-input').focus(); + }, + }, + template: ` +
{{ proposalItem.value }}
` +}); + Vue.component('result-view', { props: ['searchBar', 'resultView'], @@ -267,12 +410,7 @@ Vue.component('search-bar', { }, template: `