TOBAGO-2001: Evaluate a suggest implemenation without jQuery - Replace suggest implementation with vaadin
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SuggestRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SuggestRenderer.java
index 95bf01d..1fbc1c9 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SuggestRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SuggestRenderer.java
@@ -29,6 +29,7 @@
import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
+import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
import org.apache.myfaces.tobago.util.ComponentUtils;
import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
import org.slf4j.Logger;
@@ -121,12 +122,17 @@
writer.writeAttribute(CustomAttributes.UPDATE, suggest.isUpdate());
writer.writeAttribute(CustomAttributes.TOTAL_COUNT, totalCount);
writer.writeAttribute(CustomAttributes.LOCAL_MENU, suggest.isLocalMenu());
- writer.writeAttribute(CustomAttributes.DATA, JsonUtils.encode(array), true);
+ writer.writeAttribute(CustomAttributes.ITEMS, JsonUtils.encode(array), true);
if (LOG.isDebugEnabled()) {
LOG.debug("suggest list: {}", JsonUtils.encode(array));
}
+ writer.startElement(HtmlElements.INPUT);
+ writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
+ writer.writeAttribute(HtmlAttributes.NAME, clientId, false);
+ writer.endElement(HtmlElements.INPUT);
+
writer.endElement(HtmlElements.TOBAGO_SUGGEST);
}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
index d6a003d..7b8030b 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
@@ -24,7 +24,6 @@
COLLAPSE_ACTION("collapse-action"),
COLLAPSE_TARGET("collapse-target"),
CONFIRMATION("confirmation"),
- DATA("data"),
DELAY("delay"),
EVENT("event"),
/**
@@ -37,6 +36,7 @@
* The index of the tab inside the tab group.
*/
INDEX("index"),
+ ITEMS("items"),
LOCALE("locale"),
LOCAL_MENU("local-menu"),
MAX_ITEMS("max-items"),
diff --git a/tobago-core/src/main/resources/scss/_tobago.scss b/tobago-core/src/main/resources/scss/_tobago.scss
index 95017c9..0528fce 100644
--- a/tobago-core/src/main/resources/scss/_tobago.scss
+++ b/tobago-core/src/main/resources/scss/_tobago.scss
@@ -1384,94 +1384,6 @@
display: none;
}
-.typeahead {
- background-color: #FFFFFF;
-
- &:focus {
- border: 2px solid #0097CF;
- }
-}
-
-.tt-query {
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset;
-}
-
-.tt-hint {
- color: #aaaaaa;
-}
-
-.tt-menu {
- display: none;
- position: absolute;
- z-index: $zindex-popover;
- background-color: #FFFFFF;
- border: 1px solid rgba(0, 0, 0, 0.2);
- border-radius: 3px;
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- margin-top: 1px;
- padding: 8px 0;
-}
-
-.tt-open {
- display: block;
-}
-
-.twitter-typeahead .tt-open {
- /* for 'localMenu' - if suggest menu rendered directly on component - not in .tobago-page-menuStore */
- min-width: 100%;
-}
-
-.tt-empty {
- display: none;
-}
-
-.tt-suggestion {
- padding: 3px 20px;
-
- &:hover {
- background-color: #0097CF;
- color: #FFFFFF;
- cursor: pointer;
- }
-
- &.tt-cursor {
- background-color: #0097CF;
- color: #FFFFFF;
- }
-
- p {
- margin: 0;
- }
-}
-
-.twitter-typeahead {
- display: flex !important;
- flex-grow: 1;
-}
-
-.input-group > .twitter-typeahead {
- /* suggest in center of an input group */
- > .tobago-in {
- width: 100%;
- border-radius: 0;
- }
-
- &:first-child {
- /* suggest on the left of an input group */
- > .tobago-in {
- border-top-left-radius: $border-radius;
- border-bottom-left-radius: $border-radius;
- }
- }
- &:last-child {
- /* suggest on the right of an input group */
- > .tobago-in {
- border-top-right-radius: $border-radius;
- border-bottom-right-radius: $border-radius;
- }
- }
-}
-
/* tab / tab-group ----------------------------------------------------------------- */
.tobago-tabGroup, .tobago-tab, .tobago-tab-content {
}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/20-suggest/Suggest.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/20-suggest/Suggest.xhtml
index 549d76b..8459a0a 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/20-suggest/Suggest.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/010-input/20-suggest/Suggest.xhtml
@@ -51,6 +51,7 @@
<tc:selectItems value="#{suggestController.solarObjects}" var="name" itemValue="#{name}"/>
</tc:suggest>
</tc:in>
+ <h1>Warning: Tab key doesn't work here!</h1> <!-- fixme -->
</tc:section>
<tc:section label="Advanced">
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/package.json b/tobago-theme/tobago-theme-standard/src/main/npm/package.json
index d79929c..1c79281 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/package.json
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/package.json
@@ -31,7 +31,8 @@
"test": "jest"
},
"dependencies": {
- "@vaadin/vaadin-date-picker": "^4.0.5"
+ "@vaadin/vaadin-date-picker": "^4.0.5",
+ "@vaadin/vaadin-combo-box": "^5.0.9"
},
"devDependencies": {
"@babel/core": "^7.4.5",
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/declare.d.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/declare.d.ts
index 152dc7e..ea981ed 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/declare.d.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/declare.d.ts
@@ -24,6 +24,4 @@
popover(data?: any, options?: any): JQuery;
modal(data?: any, options?: any): JQuery;
-
- typeahead(data?: any, options?: any): JQuery;
}
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-all.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-all.ts
index e9f78c5..b8cb52b 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-all.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-all.ts
@@ -23,7 +23,7 @@
import "@vaadin/vaadin-date-picker/vaadin-date-picker-light.js";
// import "@vaadin/vaadin-date-time-picker";
// import "@vaadin/vaadin-time-picker";
-// import "@vaadin/vaadin-combo-box";
+import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
// import "moment"; //XXX moment seems not to work with rollup.js, need to re-check
import "./tobago-date";
import "./tobago-command";
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-suggest.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-suggest.ts
index 31ceeea..f935664 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-suggest.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-suggest.ts
@@ -15,70 +15,25 @@
* limitations under the License.
*/
-import {DomUtils} from "./tobago-utils";
+import {ComboBoxLightElement} from "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
class Suggest extends HTMLElement {
- static asyncResults: (data: string[]) => void;
+ static callback: (items: String[], size: number) => {};// todo string vs String
- hiddenInput: HTMLInputElement;
-
- static loadFromServer = function (input: HTMLInputElement): (query, syncResults, asyncResults) => void {
-
- let timeout;
-
- return function findMatches(query, syncResults, asyncResults): void {
-
- const root = input.getRootNode() as ShadowRoot | Document;
- let suggest = root.getElementById(input.dataset.tobagoSuggestFor) as Suggest;
-
- // todo: suggest.hiddenInput.value should contain the last query value
- if (suggest.hiddenInput.value !== query) {
-
- if (timeout) {
- clearTimeout(timeout);
- }
-
- const delay = suggest.delay;
-
- timeout = setTimeout(function (): void {
- suggest.hiddenInput.value = query;
- Suggest.asyncResults = asyncResults;
- delete suggest.dataset.tobagoSuggestData;
- console.info("query: '" + query + "'");
-
- jsf.ajax.request(
- suggest.id,
- null, // todo: event?
- {
- "javax.faces.behavior.event": "suggest",
- execute: suggest.id,
- render: suggest.id
- });
- }, delay);
-
- }
- };
- };
-
- static fromClient = function (data): (query, syncResults) => void {
- return (query, syncResults): void => {
- const result = [];
- for (let i = 0; i < data.length; i++) {
- if (data[i].indexOf(query) >= 0) {
- result.push(data[i]);
- }
- }
- syncResults(result);
- };
- };
+ static timeout: number;
constructor() {
super();
- this.hiddenInput = document.createElement("input");
- this.hiddenInput.setAttribute("type", "hidden");
- this.hiddenInput.setAttribute("name", this.id);
- this.appendChild(this.hiddenInput);
+ }
+
+ get hiddenInput(): HTMLInputElement {
+ return this.querySelector(":scope > input[type=hidden]");
+ }
+
+ get suggestInput(): HTMLInputElement {
+ const root = this.getRootNode() as ShadowRoot | Document;
+ return root.getElementById(this.for) as HTMLInputElement;
}
get for(): string {
@@ -133,12 +88,12 @@
this.setAttribute("total-count", String(totalCount));
}
- get data(): string[] {
- return JSON.parse(this.getAttribute("data"));
+ get items(): string[] {
+ return JSON.parse(this.getAttribute("items"));
}
- set data(data: string[]) {
- this.setAttribute("data", JSON.stringify(data));
+ set items(items: string[]) {
+ this.setAttribute("items", JSON.stringify(items));
}
get localMenu(): boolean {
@@ -154,76 +109,65 @@
}
connectedCallback(): void {
- const root = this.getRootNode() as ShadowRoot | Document;
- const input = root.getElementById(this.for) as HTMLInputElement;
- const $input = jQuery(input);
- if (this.update && input.classList.contains("tt-input")) { // already initialized: so only update data
- if (Suggest.asyncResults) {
- Suggest.asyncResults(this.data);
- Suggest.asyncResults = null;
- }
- } else { // new
- input.dataset.tobagoSuggestFor = this.id;
- input.autocomplete = "off";
+ let vaadinComboBox: ComboBoxLightElement = this.suggestInput.parentElement;
- let source;
- if (this.update) {
- source = Suggest.loadFromServer(input);
+ if (vaadinComboBox.tagName !== "VAADIN-COMBO-BOX-LIGHT") { // new
+ vaadinComboBox = document.createElement("vaadin-combo-box-light");
+ vaadinComboBox.attrForValue = "value";
+ vaadinComboBox.allowCustomValue = true;
+ vaadinComboBox.readOnly = this.suggestInput.readOnly;
+ vaadinComboBox.disabled = this.suggestInput.disabled;
+ this.suggestInput.classList.add("input"); // todo do this in SuggestRenderer?
+ const parent = this.suggestInput.parentElement;
+ vaadinComboBox.appendChild(this.suggestInput);
+ parent.appendChild(vaadinComboBox);
+
+ vaadinComboBox.dataProvider = function dataProvider(
+ params: { page: number, pageSize: number, filter: string },
+ callback: (items: String[], size: number) => {}): void {
+ console.info("call for data: %o", params);
+ console.info("vaadinComboBox id: %s", vaadinComboBox.id);
+ const suggest = vaadinComboBox.closest("tobago-in").querySelector("tobago-suggest") as Suggest;
+ suggest.hiddenInput.value = params.filter;
+ if (suggest.update) {
+ if (params.filter.length >= suggest.minChars) {
+ if (Suggest.timeout) {
+ clearTimeout(Suggest.timeout);
+ }
+ Suggest.timeout = window.setTimeout(function (): void {
+ Suggest.callback = callback;
+ jsf.ajax.request(
+ suggest.id,
+ null, // todo: event?
+ {
+ "javax.faces.behavior.event": "suggest",
+ execute: suggest.id,
+ render: suggest.id
+ });
+ }, suggest.delay);
+ } else {
+ callback([], 0);
+ }
+ } else {
+ const items = suggest.items;
+ const filteredItems:string[] = [];
+ const lowerFilter = params.filter.toLocaleLowerCase();
+ for (const item of items) {
+ if (item.toLowerCase().indexOf(lowerFilter) > -1) {
+ filteredItems.push(item);
+ }
+ }
+ callback(filteredItems, filteredItems.length);
+ }
+ };
+ } else { // already initialized: so update items (from AJAX) only
+ if (Suggest.callback) {
+ Suggest.callback(this.items, this.totalCount);
+ Suggest.callback = null;
} else {
- source = Suggest.fromClient(this.data);
+ console.warn("Missing Suggest.callback!");
}
-
- let suggestPopup = root.getElementById(this.id + "::popup");
- if (suggestPopup) {
- suggestPopup.parentElement.removeChild(suggestPopup);
- }
- suggestPopup = document.createElement("div");
- suggestPopup.id = this.id + "::popup";
- suggestPopup.classList.add("tt-menu", "tt-empty");
- root.querySelector(".tobago-page-menuStore").appendChild(suggestPopup);
-
- const menu = this.localMenu ? null : suggestPopup;
-
- $input.typeahead({
- menu: menu,
- minLength: this.minChars,
- hint: true,// todo
- highlight: true // todo
- }, {
- //name: 'test',// todo
- limit: this.maxItems,
- source: source
- });
- // old with jQuery:
- $input.on("typeahead:change", function (event: JQuery.Event): void {
- const input = this;
- input.dispatchEvent(new Event("change"));
- });
- // new without jQuery:
- // input.addEventListener("typeahead:change", (event: Event) => {
- // const input = event.currentTarget as HTMLInputElement;
- // input.dispatchEvent(new Event("change"));
- // });
-
- // old with jQuery:
- $input.on("typeahead:open", function (event: JQuery.Event): void {
- const input = this;
- const suggestPopup = root.getElementById(input.dataset.tobagoSuggestFor + "::popup");
- suggestPopup.style.top = DomUtils.offset(input).top + input.offsetHeight + "px";
- suggestPopup.style.left = DomUtils.offset(input).left + "px";
- suggestPopup.style.minWidth = input.offsetWidth + "px";
- });
-
- // new without jQuery:
- // input.addEventListener("typeahead:open", (event: Event) => {
- // const input = event.currentTarget as HTMLInputElement;
- // const suggestPopup = document.getElementById(input.dataset.tobagoSuggestFor + "::popup");
- // suggestPopup.style.top = DomUtils.offset(input).top + input.offsetHeight + "px";
- // suggestPopup.style.left = DomUtils.offset(input).left + "px";
- // suggestPopup.style.minWidth = input.offsetWidth + "px";
- // });
-
}
}
}