rewrite autocomplete in vue.js
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: `
|
||||
<div id="search-input-wrapper">
|
||||
<input
|
||||
id="search-input"
|
||||
autocomplete="off"
|
||||
v-model="searchBar.query"
|
||||
@keyup="onChange"
|
||||
@keyup.down.up="selectUpDown"
|
||||
@keydown.enter="selectOrSubmit"
|
||||
@keyup.esc="showProposals = false"
|
||||
@keyup.enter="noop"
|
||||
@keydown.prevent.arrowUp.arrowDown="noop"
|
||||
@focus="onChange"
|
||||
placeholder="field=value and anotherField=anotherValue" />
|
||||
<div
|
||||
id="proposals"
|
||||
v-show="searchBar.proposals.length > 0 && showProposals"
|
||||
@keyup.prevent.down.up="selectUpDown"
|
||||
>
|
||||
<proposal-item
|
||||
v-for="item in searchBar.proposals"
|
||||
v-bind:key="item.index"
|
||||
v-bind:proposalItem="item"
|
||||
v-bind:highlightedProposal="highlightedProposal"
|
||||
></proposal-item>
|
||||
</div>
|
||||
</div>`
|
||||
});
|
||||
|
||||
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: `
|
||||
<div
|
||||
v-bind:id="proposalItem.id"
|
||||
v-bind:class="{highlighted: isHighlighted }"
|
||||
v-on:click="selectProposal"
|
||||
>{{ proposalItem.value }}</div>`
|
||||
});
|
||||
|
||||
|
||||
Vue.component('result-view', {
|
||||
props: ['searchBar', 'resultView'],
|
||||
@@ -267,12 +410,7 @@ Vue.component('search-bar', {
|
||||
},
|
||||
template: `
|
||||
<form id="search-bar" v-on:submit.prevent.stop>
|
||||
<div id="search-input-wrapper">
|
||||
<input
|
||||
id="search-input"
|
||||
v-model="searchBar.query"
|
||||
placeholder="field=value and anotherField=anotherValue"/>
|
||||
</div>
|
||||
<search-bar-query v-bind="{ 'searchBar': searchBar }"></search-bar-query>
|
||||
<div id="filter-bar">
|
||||
<label for="search-group-by-0">Group:</label>
|
||||
<group-by-item
|
||||
@@ -422,7 +560,9 @@ var rootView = new Vue({
|
||||
var data = {
|
||||
|
||||
searchBar: {
|
||||
query: 'pod=vapfinra01 and method = ViewService.findFieldViewGroup',
|
||||
query: 'pod=vapfinra01 and method = ViewService.find',
|
||||
proposals: [],
|
||||
showProposals: false,
|
||||
groupByKeys: [],
|
||||
splitByKeys: {
|
||||
'selected': 'method',
|
||||
|
||||
Reference in New Issue
Block a user