From 1755562a848638075048e9da7d0529c9cbf3edfe Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 8 Apr 2018 14:06:13 +0200 Subject: [PATCH] do not move the cursor to the end when applying a proposal --- .../org/lucares/pdb/datastore/Proposal.java | 47 +++++++--- .../pdb/datastore/internal/Proposer.java | 21 +---- .../lang/QueryCompletionPdbLangParser.java | 19 ++++- .../datastore/internal/FolderStorageTest.java | 26 +++--- .../pdb/datastore/internal/ProposerTest.java | 40 ++++----- .../java/org/lucares/pdbui/PdbController.java | 3 +- .../pdbui/domain/AutocompleteProposal.java | 19 +++-- pdb-ui/src/main/resources/resources/js/ui.js | 85 ++++++++++++++----- 8 files changed, 167 insertions(+), 93 deletions(-) diff --git a/data-store/src/main/java/org/lucares/pdb/datastore/Proposal.java b/data-store/src/main/java/org/lucares/pdb/datastore/Proposal.java index 7980d09..40137c7 100644 --- a/data-store/src/main/java/org/lucares/pdb/datastore/Proposal.java +++ b/data-store/src/main/java/org/lucares/pdb/datastore/Proposal.java @@ -7,17 +7,26 @@ public class Proposal implements Comparable { private final boolean hasResults; - public Proposal(final String proposedTag, final String proposedQuery, final boolean hasResults) { + private final String newQuery; + + private final int newCaretPosition; + + public Proposal(final String proposedTag, final String proposedQuery, final boolean hasResults, + final String newQuery, final int newCaretPosition) { super(); this.proposedTag = proposedTag; this.proposedQuery = proposedQuery; this.hasResults = hasResults; + this.newQuery = newQuery; + this.newCaretPosition = newCaretPosition; } public Proposal(final Proposal proposal, final boolean hasResults) { this.proposedTag = proposal.proposedTag; this.proposedQuery = proposal.proposedQuery; this.hasResults = hasResults; + this.newQuery = proposal.newQuery; + this.newCaretPosition = proposal.newCaretPosition; } public String getProposedTag() { @@ -32,38 +41,50 @@ public class Proposal implements Comparable { return hasResults; } - @Override - public String toString() { - return "Proposal [proposedTag:" + proposedTag + ", proposedQuery:" + proposedQuery + ", hasResults=" + hasResults - + "]"; + public String getNewQuery() { + return newQuery; } - + public int getNewCaretPosition() { + return newCaretPosition; + } + @Override + public String toString() { + return "Proposal [proposedTag=" + proposedTag + ", proposedQuery=" + proposedQuery + ", hasResults=" + + hasResults + ", newQuery=" + newQuery + ", newCaretPosition=" + newCaretPosition + "]"; + } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (hasResults ? 1231 : 1237); - result = prime * result - + ((proposedQuery == null) ? 0 : proposedQuery.hashCode()); - result = prime * result - + ((proposedTag == null) ? 0 : proposedTag.hashCode()); + result = prime * result + newCaretPosition; + result = prime * result + ((newQuery == null) ? 0 : newQuery.hashCode()); + result = prime * result + ((proposedQuery == null) ? 0 : proposedQuery.hashCode()); + result = prime * result + ((proposedTag == null) ? 0 : proposedTag.hashCode()); return result; } @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; - Proposal other = (Proposal) obj; + final Proposal other = (Proposal) obj; if (hasResults != other.hasResults) return false; + if (newCaretPosition != other.newCaretPosition) + return false; + if (newQuery == null) { + if (other.newQuery != null) + return false; + } else if (!newQuery.equals(other.newQuery)) + return false; if (proposedQuery == null) { if (other.proposedQuery != null) return false; @@ -78,7 +99,7 @@ public class Proposal implements Comparable { } @Override - public int compareTo(Proposal o) { + public int compareTo(final Proposal o) { return proposedTag.compareTo(o.getProposedTag()); } } diff --git a/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java b/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java index 54707d6..7c81106 100644 --- a/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java +++ b/data-store/src/main/java/org/lucares/pdb/datastore/internal/Proposer.java @@ -1,8 +1,6 @@ package org.lucares.pdb.datastore.internal; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; @@ -46,23 +44,12 @@ public class Proposer { } private SortedSet proposeForAllKeys() { - final SortedSet result; + final SortedSet result = new TreeSet<>(); final List fields = dataStore.getAvailableFields(); - final Map fieldToQuery = CollectionUtils.createMapFromKeys(fields, f -> f + "=*"); - - result = toProposalsForQueries(fieldToQuery); - return result; - } - - private SortedSet toProposalsForQueries(final Map keyToQuery) { - - final SortedSet result = new TreeSet<>(); - for (final Entry e : keyToQuery.entrySet()) { - final String key = e.getKey(); - final String query = e.getValue(); - - final Proposal proposal = new Proposal(key, query, true); + for (final String field : fields) { + final String newQuery = field + "="; + final Proposal proposal = new Proposal(field, field + "=*", true, newQuery, newQuery.length()); result.add(proposal); } 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 8c4e3ce..16662cf 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,6 +26,8 @@ import org.slf4j.LoggerFactory; public class QueryCompletionPdbLangParser extends PdbLangParser { + private final static String CARET_MARKER = "\ue002"; // third character in the private use area + private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.autocomplete"); public class Listener implements PdbLangListener, ANTLRErrorListener { @@ -64,7 +66,8 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { final StringBuilder newQuery = new StringBuilder(query); newQuery.replace(start, end + 1, v + " "); // insert the terminal into the query - return new Proposal(v, newQuery.toString(), false); + return new Proposal(v, newQuery.toString(), false, newQuery.toString(), + start + v.length() + 1); }).map(p -> { final int count = dataStore.count(p.getProposedQuery()); @@ -101,13 +104,15 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { final StringBuilder newQuery = new StringBuilder(query); newQuery.replace(charPositionInLine, charPositionInLine + text.length(), " and "); - proposals.add(new Proposal(" and ", newQuery.toString(), true)); + proposals.add(new Proposal(" and ", newQuery.toString(), true, newQuery.toString(), + charPositionInLine + text.length())); } if ("or".startsWith(text)) { final StringBuilder newQuery = new StringBuilder(query); newQuery.replace(charPositionInLine, charPositionInLine + text.length(), " or "); - proposals.add(new Proposal(" or ", newQuery.toString(), true)); + proposals.add(new Proposal(" or ", newQuery.toString(), true, newQuery.toString(), + charPositionInLine + text.length())); } } } @@ -121,7 +126,13 @@ public class QueryCompletionPdbLangParser extends PdbLangParser { matchingKeys.stream()// .map(key -> { - return new Proposal(key, String.format(newQueryPattern, key + "=* "), false); + final String newQueryWithMarker = String.format(newQueryPattern, key + "=" + CARET_MARKER) + .replaceAll("\\s+", " "); + final int newCaretPosition = newQueryWithMarker.indexOf(CARET_MARKER); + final String newQuery = newQueryWithMarker.replace(CARET_MARKER, ""); + + return new Proposal(key, String.format(newQueryPattern, key + "=* "), false, newQuery, + newCaretPosition); }).map(p -> { final String proposedQuery = p.getProposedQuery(); diff --git a/data-store/src/test/java/org/lucares/pdb/datastore/internal/FolderStorageTest.java b/data-store/src/test/java/org/lucares/pdb/datastore/internal/FolderStorageTest.java index a1e484d..5d35c6b 100644 --- a/data-store/src/test/java/org/lucares/pdb/datastore/internal/FolderStorageTest.java +++ b/data-store/src/test/java/org/lucares/pdb/datastore/internal/FolderStorageTest.java @@ -41,15 +41,15 @@ public class FolderStorageTest { final List actualFiles = getPathsRelativeToDataDirectory(); final List expectedFiles = Arrays.asList(// - Paths.get("0", "0", "a" + SUFFIX), // - Paths.get("0", "0", "b" + SUFFIX), // - Paths.get("0", "1", "c" + SUFFIX), // - Paths.get("0", "1", "d" + SUFFIX), // - Paths.get("1", "0", "e" + SUFFIX), // - Paths.get("1", "0", "f" + SUFFIX), // - Paths.get("1", "1", "g" + SUFFIX), // - Paths.get("1", "1", "h" + SUFFIX), // - Paths.get("2", "0", "i" + SUFFIX)// The first level might + Paths.get("0", "0", "a$" + SUFFIX), // + Paths.get("0", "0", "b$" + SUFFIX), // + Paths.get("0", "1", "c$" + SUFFIX), // + Paths.get("0", "1", "d$" + SUFFIX), // + Paths.get("1", "0", "e$" + SUFFIX), // + Paths.get("1", "0", "f$" + SUFFIX), // + Paths.get("1", "1", "g$" + SUFFIX), // + Paths.get("1", "1", "h$" + SUFFIX), // + Paths.get("2", "0", "i$" + SUFFIX)// The first level might // overflow ); @@ -65,10 +65,10 @@ public class FolderStorageTest { final List actualFiles = getPathsRelativeToDataDirectory(); final List expectedFiles = Arrays.asList(// - Paths.get("0", "0", "a" + SUFFIX), // - Paths.get("0", "0", "a1" + SUFFIX), // - Paths.get("0", "0", "a2" + SUFFIX), // - Paths.get("0", "1", "a" + SUFFIX)// + Paths.get("0", "0", "a$" + SUFFIX), // + Paths.get("0", "0", "a$1" + SUFFIX), // + Paths.get("0", "0", "a$2" + SUFFIX), // + Paths.get("0", "1", "a$" + SUFFIX)// ); Assert.assertEquals(actualFiles, expectedFiles); diff --git a/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java b/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java index 92d196f..c45f2de 100644 --- a/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java +++ b/data-store/src/test/java/org/lucares/pdb/datastore/internal/ProposerTest.java @@ -66,38 +66,38 @@ public class ProposerTest { public void testEmptyQuery() throws Exception { assertProposals("", 0, // - new Proposal("name", "name=*", true), // - new Proposal("bird", "bird=*", true), // - new Proposal("dog", "dog=*", true)// + new Proposal("name", "name=*", true, "name=", 5), // + new Proposal("bird", "bird=*", true, "bird=", 5), // + new Proposal("dog", "dog=*", true, "dog=", 4)// ); assertProposals(" ", 1, // - new Proposal("name", "name=*", true), // - new Proposal("bird", "bird=*", true), // - new Proposal("dog", "dog=*", true)// + new Proposal("name", "name=*", true, "name=", 5), // + new Proposal("bird", "bird=*", true, "bird=", 5), // + new Proposal("dog", "dog=*", true, "dog=", 4)// ); } public void testPrefixOfKey() throws Exception { assertProposals("bi", 2, // - new Proposal("bird", "bird=* ", true) // + new Proposal("bird", "bird=* ", true, "bird=", 5) // ); assertProposals("bird", 4, // - new Proposal("bird", "bird=* ", true) // + new Proposal("bird", "bird=* ", true, "bird=", 5) // ); } public void testPrefixOfValue() throws Exception { assertProposals("name =Tim", 9, // - new Proposal("Timothy", "name =Timothy ", true)); + new Proposal("Timothy", "name =Timothy ", true, "name =Timothy ", 14)); assertProposals("name =Je", 8, // - new Proposal("Jennifer", "name =Jennifer ", true), // - new Proposal("Jenny", "name =Jenny ", true) // + new Proposal("Jennifer", "name =Jennifer ", true, "name =Jennifer ", 15), // + new Proposal("Jenny", "name =Jenny ", true, "name =Jenny ", 12) // ); assertProposals("bird=eagle and n", 16, // - new Proposal("name", "bird=eagle and name=* ", true) // + new Proposal("name", "bird=eagle and name=* ", true, "bird=eagle and name=", 20) // ); /* */ @@ -105,18 +105,18 @@ public class ProposerTest { public void testProposalOnEmptyValuePrefix() throws Exception { assertProposals("name=", 5, // - new Proposal("Jennifer", "name=Jennifer ", true), // - new Proposal("Jenny", "name=Jenny ", true), // - new Proposal("Tim", "name=Tim ", true), // - new Proposal("Timothy", "name=Timothy ", true) // + new Proposal("Jennifer", "name=Jennifer ", true, "name=Jennifer ", 14), // + new Proposal("Jenny", "name=Jenny ", true, "name=Jenny ", 11), // + new Proposal("Tim", "name=Tim ", true, "name=Tim ", 9), // + new Proposal("Timothy", "name=Timothy ", true, "name=Timothy ", 13) // ); } public void testProposalOnEmptyKeyPrefix() throws Exception { assertProposals("name=* and ", 11, // - new Proposal("name", "name=* and name=* ", true), // - new Proposal("bird", "name=* and bird=* ", true), // - new Proposal("dog", "name=* and dog=* ", true) // + new Proposal("name", "name=* and name=* ", true, "name=* and name=", 16), // + new Proposal("bird", "name=* and bird=* ", true, "name=* and bird=", 16), // + new Proposal("dog", "name=* and dog=* ", true, "name=* and dog=", 15) // ); } @@ -130,6 +130,6 @@ public class ProposerTest { System.out.println("\n\n--- " + query + " ---"); System.out.println("actual : " + actual); System.out.println("expected: " + expectedList); - Assert.assertEquals(expectedList, actual); + Assert.assertEquals(actual, expectedList); } } 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 151a400..94a9390 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java @@ -161,7 +161,8 @@ public class PdbController implements HardcodedValues { for (final Proposal proposal : proposals) { final AutocompleteProposal e = new AutocompleteProposal(); e.setValue(proposal.getProposedTag()); - e.setProposedQuery(proposal.getProposedQuery()); + e.setNewQuery(proposal.getNewQuery()); + e.setNewCaretPosition(proposal.getNewCaretPosition()); result.add(e); } diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java index 899e97f..f9f0ed6 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/domain/AutocompleteProposal.java @@ -2,7 +2,8 @@ package org.lucares.pdbui.domain; public class AutocompleteProposal { private String value; - private String proposedQuery; + private String newQuery; + private int newCaretPosition; public String getValue() { return value; @@ -17,11 +18,19 @@ public class AutocompleteProposal { return value; } - public void setProposedQuery(final String proposedQuery) { - this.proposedQuery = proposedQuery; + public int getNewCaretPosition() { + return newCaretPosition; } - public String getProposedQuery() { - return proposedQuery; + public void setNewCaretPosition(final int newCaretPosition) { + this.newCaretPosition = newCaretPosition; + } + + public String getNewQuery() { + return newQuery; + } + + public void setNewQuery(final String newQuery) { + this.newQuery = newQuery; } } diff --git a/pdb-ui/src/main/resources/resources/js/ui.js b/pdb-ui/src/main/resources/resources/js/ui.js index 93d276b..df2e448 100644 --- a/pdb-ui/src/main/resources/resources/js/ui.js +++ b/pdb-ui/src/main/resources/resources/js/ui.js @@ -1,5 +1,16 @@ 'use strict'; +function ffHasParentWithId(el, id) { + if (el.id == id){ + return true; + } + else if (el.parentNode) { + return ffHasParentWithId(el.parentNode, id); + } + + return false; +}; + window.onload=function(){ Vue.config.keyCodes.arrowUp = 38; @@ -12,18 +23,31 @@ Vue.component('search-bar-query', { 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); + const isSearchInput = event.path + ? event.path.map((e) => e.id == "search-input-wrapper") + .reduce((accumulator, currentValue) => accumulator || currentValue) + : ffHasParentWithId(event.explicitOriginalTarget, "search-input-wrapper"); if(!isSearchInput){ data.searchBar.proposals = []; - data.searchBar.showProposals = false; } }); + document.onkeydown = function(evt) { + evt = evt || window.event; + var isEscape = false; + if ("key" in evt) { + isEscape = (evt.key == "Escape" || evt.key == "Esc"); + } else { + isEscape = (evt.keyCode == 27); + } + if (isEscape) { + data.searchBar.proposals = []; + } + }; + }, data: function() { return { - showProposals: false, highlightedProposal: -1, }; }, @@ -32,18 +56,23 @@ Vue.component('search-bar-query', { if (event.key == 'ArrowDown' || event.key == 'ArrowUp' || event.key == 'Enter' || event.key == 'Escape'){ return; } - //console.log(event.key); - - var vm = this; + console.log(event); + + this.autocomplete(this, event); + }, + autocomplete: function(vm, event) { + if(this.autocompleteTimeout) { window.clearTimeout(this.autocompleteTimeout); - event.preventDefault(); + if (event) { + event.preventDefault(); + } } vm.showProposals = false; var expectedRequestNumber = ++vm.requestNumber; - this.autocompleteTimeout = window.setTimeout(function() { + vm.autocompleteTimeout = window.setTimeout(function() { var caretIndex = document.getElementById('search-input').selectionStart + 1; var request='autocomplete?caretIndex=' + caretIndex + '&query='+encodeURIComponent(data.searchBar.query); @@ -67,7 +96,7 @@ Vue.component('search-bar-query', { var errorCallback = function(e) { console.log("FAILED: " + JSON.parse(e.responseText).message); - vm.showProposals=false; + vm.proposals=[]; }; getJson(request, {}, successCallback, errorCallback); @@ -75,11 +104,23 @@ Vue.component('search-bar-query', { 300); }, selectOrSubmit: function(event) { + const vm = this; if (this.highlightedProposal >= 0){ - var proposal = data.searchBar.proposals[this.highlightedProposal]; - data.searchBar.query = proposal.proposedQuery; - this.showProposals = false; event.preventDefault(); + var proposal = data.searchBar.proposals[this.highlightedProposal]; + data.searchBar.query = proposal.newQuery; + data.searchBar.proposals = []; + + console.log(proposal.newCaretPosition); + + Vue.nextTick(function () { + var el = document.getElementById('search-input'); + el.select(); + el.selectionStart=proposal.newCaretPosition; + el.selectionEnd=proposal.newCaretPosition; + + vm.autocomplete(vm); + }); } }, selectUpDown: function(event) { @@ -109,7 +150,7 @@ Vue.component('search-bar-query', { placeholder="field=value and anotherField=anotherValue" />
-