rewrite autocomplete in vue.js
This commit is contained in:
@@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
public class QueryCompletionPdbLangParser extends PdbLangParser {
|
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 {
|
public class Listener implements PdbLangListener, ANTLRErrorListener {
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ public class QueryCompletionPdbLangParser extends PdbLangParser {
|
|||||||
return new Proposal(p, count > 0);
|
return new Proposal(p, count > 0);
|
||||||
}).forEach(proposals::add);
|
}).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);
|
(System.nanoTime() - startTime) / 1_000_000.0);
|
||||||
} else if (_ctx instanceof IdentifierExpressionContext) {
|
} else if (_ctx instanceof IdentifierExpressionContext) {
|
||||||
final long startTime = System.nanoTime();
|
final long startTime = System.nanoTime();
|
||||||
@@ -83,7 +83,7 @@ public class QueryCompletionPdbLangParser extends PdbLangParser {
|
|||||||
|
|
||||||
addProposalsForKeys(propertyKeyPrefix, newQueryPattern.toString());
|
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);
|
(System.nanoTime() - startTime) / 1_000_000.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.lucares.pdbui;
|
package org.lucares.pdbui;
|
||||||
|
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
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 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 Plotter plotter;
|
||||||
private final PerformanceDb db;
|
private final PerformanceDb db;
|
||||||
|
|
||||||
@@ -111,7 +107,6 @@ public class PdbController implements HardcodedValues {
|
|||||||
|
|
||||||
@RequestMapping(path = "/autocomplete", //
|
@RequestMapping(path = "/autocomplete", //
|
||||||
method = RequestMethod.GET, //
|
method = RequestMethod.GET, //
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, //
|
|
||||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
|
||||||
)
|
)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
|
|||||||
@@ -84,6 +84,26 @@ body {
|
|||||||
padding: 2px;
|
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 {
|
#search-limit-value {
|
||||||
width: 4em;
|
width: 4em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,149 @@
|
|||||||
|
|
||||||
window.onload=function(){
|
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', {
|
Vue.component('result-view', {
|
||||||
props: ['searchBar', 'resultView'],
|
props: ['searchBar', 'resultView'],
|
||||||
@@ -267,12 +410,7 @@ Vue.component('search-bar', {
|
|||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<form id="search-bar" v-on:submit.prevent.stop>
|
<form id="search-bar" v-on:submit.prevent.stop>
|
||||||
<div id="search-input-wrapper">
|
<search-bar-query v-bind="{ 'searchBar': searchBar }"></search-bar-query>
|
||||||
<input
|
|
||||||
id="search-input"
|
|
||||||
v-model="searchBar.query"
|
|
||||||
placeholder="field=value and anotherField=anotherValue"/>
|
|
||||||
</div>
|
|
||||||
<div id="filter-bar">
|
<div id="filter-bar">
|
||||||
<label for="search-group-by-0">Group:</label>
|
<label for="search-group-by-0">Group:</label>
|
||||||
<group-by-item
|
<group-by-item
|
||||||
@@ -422,7 +560,9 @@ var rootView = new Vue({
|
|||||||
var data = {
|
var data = {
|
||||||
|
|
||||||
searchBar: {
|
searchBar: {
|
||||||
query: 'pod=vapfinra01 and method = ViewService.findFieldViewGroup',
|
query: 'pod=vapfinra01 and method = ViewService.find',
|
||||||
|
proposals: [],
|
||||||
|
showProposals: false,
|
||||||
groupByKeys: [],
|
groupByKeys: [],
|
||||||
splitByKeys: {
|
splitByKeys: {
|
||||||
'selected': 'method',
|
'selected': 'method',
|
||||||
|
|||||||
Reference in New Issue
Block a user