All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.dominokit.domino.ui.forms.SuggestBox Maven / Gradle / Ivy
package org.dominokit.domino.ui.forms;
import elemental2.dom.*;
import org.dominokit.domino.ui.dropdown.DropDownMenu;
import org.dominokit.domino.ui.dropdown.DropDownPosition;
import org.dominokit.domino.ui.dropdown.DropdownAction;
import org.dominokit.domino.ui.forms.SuggestBoxStore.MissingEntryProvider;
import org.dominokit.domino.ui.forms.SuggestBoxStore.MissingSuggestProvider;
import org.dominokit.domino.ui.keyboard.KeyboardEvents;
import org.dominokit.domino.ui.loaders.Loader;
import org.dominokit.domino.ui.loaders.LoaderEffect;
import org.dominokit.domino.ui.style.Color;
import org.dominokit.domino.ui.utils.DelayedTextInput;
import org.dominokit.domino.ui.utils.HasSelectionHandler;
import org.jboss.elemento.Elements;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static elemental2.dom.DomGlobal.document;
import static elemental2.dom.DomGlobal.window;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.dominokit.domino.ui.style.Unit.px;
import static org.jboss.elemento.Elements.div;
public class SuggestBox extends AbstractValueBox, HTMLInputElement, T> implements HasSelectionHandler, SuggestItem> {
private static final String TEXT = "text";
private DropDownMenu suggestionsMenu;
private List>> selectionHandlers = new ArrayList<>();
private SuggestBoxStore store;
private HTMLDivElement loaderContainer = div().css("suggest-box-loader").element();
private Loader loader;
private boolean emptyAsNull;
private Color highlightColor;
private T value;
private int typeAheadDelay = 200;
private SuggestItem selectedItem;
private DelayedTextInput delayedTextInput;
private boolean focusOnClose = true;
private DelayedTextInput.DelayedAction delayedAction = () -> {
if (isEmpty()) {
suggestionsMenu.close();
clearValue();
} else {
search();
}
};
private boolean autoSelect = true;
public SuggestBox() {
this("");
}
public SuggestBox(String label) {
this(label, null);
}
public SuggestBox(SuggestBoxStore store) {
this("", store);
}
public SuggestBox(String label, SuggestBoxStore store) {
this(TEXT, label, store);
}
public SuggestBox(String type, String label, SuggestBoxStore store) {
super(type, label);
this.store = store;
suggestionsMenu = DropDownMenu.create(fieldContainer);
suggestionsMenu.setAppendTarget(document.body);
suggestionsMenu.setAppendStrategy(DropDownMenu.AppendStrategy.FIRST);
suggestionsMenu.setPosition(new PopupPositionTopDown());
suggestionsMenu.addCloseHandler(() -> {
if(focusOnClose){
focus();
}
});
Element element = document.querySelector(".content");
if (nonNull(element)) {
EventListener eventListener = evt -> {
suggestionsMenu.style().setWidth(element().offsetWidth + "px");
};
element.addEventListener("transitionend", eventListener);
onDetached(mutationRecord -> element.removeEventListener("transitionend", eventListener));
}
onAttached(mutationRecord -> {
suggestionsMenu.style().setWidth(element().offsetWidth + "px");
});
getFieldInputContainer().insertFirst(loaderContainer);
setLoaderEffect(LoaderEffect.IOS);
delayedTextInput = DelayedTextInput.create(getInputElement(), typeAheadDelay)
.setDelayedAction(delayedAction);
KeyboardEvents.listenOn(getInputElement())
.onArrowDown(evt -> {
suggestionsMenu.focus();
evt.preventDefault();
})
.onArrowUp(evt -> {
suggestionsMenu.focus();
evt.preventDefault();
})
.onEscape(evt -> {
focus();
evt.preventDefault();
})
.onEnter(evt -> {
if (suggestionsMenu.isOpened() && !suggestionsMenu.getFilteredAction().isEmpty()) {
evt.stopPropagation();
evt.preventDefault();
if(isAutoSelect()) {
List> filteredActions = suggestionsMenu.getFilteredAction();
suggestionsMenu.selectAt(suggestionsMenu.getActions().indexOf(filteredActions.get(0)));
filteredActions.get(0).select();
suggestionsMenu.close();
}else{
suggestionsMenu.focus();
}
}
})
.onTab(evt -> {
if (suggestionsMenu.isOpened()) {
evt.stopPropagation();
evt.preventDefault();
suggestionsMenu.focus();
}
});
}
public static SuggestBox create(SuggestBoxStore store) {
return new SuggestBox<>(store);
}
public static SuggestBox create(String label, SuggestBoxStore store) {
return new SuggestBox(label, store);
}
public final void search() {
if (store != null) {
loader.start();
suggestionsMenu.clearActions();
suggestionsMenu.close();
store.filter(getStringValue(), suggestions -> {
selectedItem = null;
suggestionsMenu.clearActions();
if (suggestions.isEmpty()) {
applyMissingEntry(getStringValue());
}
suggestions.forEach(suggestion -> {
suggestion.highlight(SuggestBox.this.getStringValue(), highlightColor);
suggestionsMenu.appendChild(dropdownAction(suggestion));
});
suggestionsMenu.open(false);
loader.stop();
});
}
}
@Override
protected HTMLInputElement createInputElement(String type) {
return Elements.input(type).element();
}
public int getTypeAheadDelay() {
return typeAheadDelay;
}
public SuggestBox setTypeAheadDelay(int delayMilliseconds) {
this.typeAheadDelay = delayMilliseconds;
this.delayedTextInput.setDelay(delayMilliseconds);
return this;
}
public DelayedTextInput.DelayedAction getDelayedAction() {
return delayedAction;
}
public SuggestBox setDelayedAction(DelayedTextInput.DelayedAction delayedAction) {
this.delayedAction = delayedAction;
this.delayedTextInput.setDelayedAction(delayedAction);
return this;
}
public SuggestBox setOnEnterAction(DelayedTextInput.DelayedAction onEnterAction) {
this.delayedTextInput.setOnEnterAction(onEnterAction);
return this;
}
@Override
protected void clearValue() {
value(null);
}
@Override
protected void doSetValue(T value) {
if (nonNull(store)) {
store.find(value, suggestItem -> {
if (nonNull(suggestItem)) {
this.value = value;
getInputElement().element().value = suggestItem.getDisplayValue();
} else {
if (!applyMissingValue(value)) {
this.value = null;
getInputElement().element().value = "";
}
}
});
}
}
private boolean applyMissingValue(T value) {
MissingSuggestProvider messingSuggestionProvider = store.getMessingSuggestionProvider();
Optional> messingSuggestion = messingSuggestionProvider.getMessingSuggestion(value);
return applyMissing(messingSuggestion);
}
private boolean applyMissingEntry(String value) {
MissingEntryProvider messingEntryProvider = store.getMessingEntryProvider();
Optional> messingSuggestion = messingEntryProvider.getMessingSuggestion(value);
return applyMissing(messingSuggestion);
}
private boolean applyMissing(Optional> messingSuggestion) {
if (messingSuggestion.isPresent()) {
SuggestItem messingSuggestItem = messingSuggestion.get();
this.value = messingSuggestItem.getValue();
getInputElement().element().value = messingSuggestItem.getDisplayValue();
return true;
}
return false;
}
@Override
public T getValue() {
if (isNull(selectedItem)) {
applyMissingEntry(getStringValue());
}
return this.value;
}
public SuggestBox setSuggestBoxStore(SuggestBoxStore store) {
this.store = store;
return this;
}
public SuggestBox setType(String type) {
getInputElement().element().type = type;
return this;
}
@Override
public String getStringValue() {
String stringValue = getInputElement().element().value;
if (stringValue.isEmpty() && isEmptyAsNull()) {
return null;
}
return stringValue;
}
private DropdownAction dropdownAction(SuggestItem suggestItem) {
DropdownAction dropdownAction = suggestItem.asDropDownAction();
dropdownAction.addSelectionHandler(value -> {
selectedItem = suggestItem;
setValue(value);
selectionHandlers.forEach(handler -> handler.onSelection(suggestItem));
suggestionsMenu.close();
});
return dropdownAction;
}
@Override
public SuggestBox addSelectionHandler(SelectionHandler> selectionHandler) {
selectionHandlers.add(selectionHandler);
return this;
}
@Override
public SuggestBox removeSelectionHandler(SelectionHandler> selectionHandler) {
selectionHandlers.remove(selectionHandler);
return this;
}
public SuggestBox setLoaderEffect(LoaderEffect loaderEffect) {
loader = Loader.create(loaderContainer, loaderEffect)
.setSize("20px", "20px")
.setRemoveLoadingText(true);
return this;
}
public Loader getLoader() {
return loader;
}
public SuggestBox setEmptyAsNull(boolean emptyAsNull) {
this.emptyAsNull = emptyAsNull;
return this;
}
public boolean isEmptyAsNull() {
return emptyAsNull;
}
public SuggestBoxStore getStore() {
return store;
}
public DelayedTextInput getDelayedTextInput() {
return delayedTextInput;
}
public DropDownMenu getSuggestionsMenu() {
return suggestionsMenu;
}
public SuggestBox setHighlightColor(Color highlightColor) {
this.highlightColor = highlightColor;
return this;
}
@Override
protected AutoValidator createAutoValidator(AutoValidate autoValidate) {
return new SuggestAutoValidator<>(this, autoValidate);
}
public boolean isAutoSelect() {
return autoSelect;
}
public SuggestBox setAutoSelect(boolean autoSelect) {
this.autoSelect = autoSelect;
return this;
}
public boolean isFocusOnClose() {
return focusOnClose;
}
public SuggestBox setFocusOnClose(boolean focusOnClose) {
this.focusOnClose = focusOnClose;
return this;
}
public static class PopupPositionTopDown implements DropDownPosition {
private DropDownPositionUp up = new DropDownPositionUp();
private DropDownPositionDown down = new DropDownPositionDown();
@Override
public void position(HTMLElement popup, HTMLElement target) {
ClientRect targetRect = target.getBoundingClientRect();
double distanceToMiddle = ((targetRect.top) - (targetRect.height / 2));
double windowMiddle = window.innerHeight;
double popupHeight = popup.getBoundingClientRect().height;
double distanceToBottom = window.innerHeight - targetRect.top;
double distanceToTop = (targetRect.top + targetRect.height);
boolean hasSpaceBelow = distanceToBottom > popupHeight;
boolean hasSpaceUp = distanceToTop > popupHeight;
if (hasSpaceUp || ((distanceToMiddle >= windowMiddle) && !hasSpaceBelow)) {
up.position(popup, target);
popup.setAttribute("popup-direction", "top");
} else {
down.position(popup, target);
popup.setAttribute("popup-direction", "down");
}
popup.style.setProperty("width", targetRect.width + "px");
}
}
public static class DropDownPositionUp implements DropDownPosition {
@Override
public void position(HTMLElement actionsMenu, HTMLElement target) {
ClientRect targetRect = target.getBoundingClientRect();
actionsMenu.style.setProperty("bottom", px.of(((window.innerHeight - targetRect.bottom + targetRect.height) - window.pageYOffset)));
actionsMenu.style.setProperty("left", px.of((targetRect.left + window.pageXOffset)));
actionsMenu.style.removeProperty("top");
}
}
public static class DropDownPositionDown implements DropDownPosition {
@Override
public void position(HTMLElement actionsMenu, HTMLElement target) {
ClientRect targetRect = target.getBoundingClientRect();
actionsMenu.style.setProperty("top", px.of((targetRect.top + targetRect.height + window.pageYOffset)));
actionsMenu.style.setProperty("left", px.of((targetRect.left + window.pageXOffset)));
actionsMenu.style.removeProperty("bottom");
}
}
private static class SuggestAutoValidator extends AutoValidator {
private SuggestBox suggestBox;
private SelectionHandler> selectionHandler;
public SuggestAutoValidator(SuggestBox suggestBox, AutoValidate autoValidate) {
super(autoValidate);
this.suggestBox = suggestBox;
}
@Override
public void attach() {
selectionHandler = option -> autoValidate.apply();
suggestBox.addSelectionHandler(selectionHandler);
}
@Override
public void remove() {
suggestBox.removeSelectionHandler(selectionHandler);
}
}
}