Skip to content

Instantly share code, notes, and snippets.

@TatuLund
Last active April 22, 2022 05:52
Show Gist options
  • Select an option

  • Save TatuLund/0c4e8ce34a62220e2ba2c654ffdd855f to your computer and use it in GitHub Desktop.

Select an option

Save TatuLund/0c4e8ce34a62220e2ba2c654ffdd855f to your computer and use it in GitHub Desktop.
Light version of Vaadin's ComboBox without lazy loading, custom value, etc.
package org.tatu.vaadin20.components.simplecombo;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.DetachEvent;
import com.vaadin.flow.component.HasHelper;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasValidation;
import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.combobox.GeneratedVaadinComboBox;
import com.vaadin.flow.data.binder.HasDataProvider;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.KeyMapper;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.shared.Registration;
import elemental.json.JsonArray;
import elemental.json.JsonFactory;
import elemental.json.JsonObject;
import elemental.json.impl.JreJsonFactory;
public class ComboBoxLight<T> extends GeneratedVaadinComboBox<ComboBoxLight<T>, T>
implements HasSize, HasValidation, HasDataProvider<T>, HasHelper {
private DataProvider<T, ?> dataProvider;
private Registration dataProviderListenerRegistration;
private ItemLabelGenerator<T> itemLabelGenerator = String::valueOf;
private final KeyMapper<T> keyMapper = new KeyMapper<>();
private static final String PROP_AUTO_OPEN_DISABLED = "autoOpenDisabled";
private static final String PROP_INPUT_ELEMENT_VALUE = "_inputElementValue";
private int customValueListenersCount;
private class CustomValueRegistration implements Registration {
private Registration delegate;
private CustomValueRegistration(Registration delegate) {
this.delegate = delegate;
}
@Override
public void remove() {
if (delegate != null) {
delegate.remove();
customValueListenersCount--;
if (customValueListenersCount == 0) {
setAllowCustomValue(false);
}
delegate = null;
}
}
}
private static <T> T presentationToModel(ComboBoxLight<T> select,
String presentation) {
if (!select.keyMapper.containsKey(presentation)) {
return null;
}
return select.keyMapper.get(presentation);
}
private static <T> String modelToPresentation(ComboBoxLight<T> select,
T model) {
if (!select.keyMapper.has(model)) {
return null;
}
return select.keyMapper.key(model);
}
public ComboBoxLight() {
super(null, null, String.class, ComboBoxLight::presentationToModel,
ComboBoxLight::modelToPresentation, true);
setItemValuePath("key");
setItemIdPath("key");
super.addCustomValueSetListener(e -> this.getElement()
.setProperty(PROP_INPUT_ELEMENT_VALUE, e.getDetail()));
super.addValueChangeListener(e -> updateSelectedKey());
}
private void updateSelectedKey() {
// Send (possibly updated) key for the selected value
getElement().executeJs("this._selectedKey=$0",
getValue() != null ? keyMapper.key(getValue()) : "");
}
public void setItemLabelGenerator(
ItemLabelGenerator<T> itemLabelGenerator) {
Objects.requireNonNull(itemLabelGenerator,
"The item label generator can not be null");
this.itemLabelGenerator = itemLabelGenerator;
reset();
}
public ItemLabelGenerator<T> getItemLabelGenerator() {
return itemLabelGenerator;
}
private void reset() {
keyMapper.removeAll();
List<String> items = getDataProvider().fetch(new Query<>())
.map(item -> keyMapper.key(item))
.collect(Collectors.toList());
JsonFactory factory = new JreJsonFactory();
JsonArray jsonItems = factory.createArray();
int i = 0;
for (String item : items) {
JsonObject object = factory.createObject();
object.put("key", item);
object.put("label", getItemLabelGenerator().apply(keyMapper.get(item)));
jsonItems.set(i++, object);
}
getElement().setPropertyJson("items", jsonItems);
}
/**
* Enables or disables the dropdown opening automatically. If {@code false}
* the dropdown is only opened when clicking the toggle button or pressing
* Up or Down arrow keys.
*
* @param autoOpen
* {@code false} to prevent the dropdown from opening
* automatically
*/
public void setAutoOpen(boolean autoOpen) {
getElement().setProperty(PROP_AUTO_OPEN_DISABLED, !autoOpen);
}
/**
* Gets whether dropdown will open automatically or not.
*
* @return @{code true} if enabled, {@code false} otherwise
*/
public boolean isAutoOpen() {
return !getElement().getProperty(PROP_AUTO_OPEN_DISABLED, false);
}
@Override
public void setAutofocus(boolean autofocus) {
super.setAutofocus(autofocus);
}
public boolean isAutofocus() {
return isAutofocusBoolean();
}
@Override
public void setPreventInvalidInput(boolean preventInvalidInput) {
super.setPreventInvalidInput(preventInvalidInput);
}
@Override
public void setLabel(String label) {
super.setLabel(label);
}
public String getLabel() {
return getLabelString();
}
@Override
public void setErrorMessage(String errorMessage) {
super.setErrorMessage(errorMessage);
}
@Override
public String getErrorMessage() {
return super.getErrorMessageString();
}
@Override
public void setInvalid(boolean invalid) {
super.setInvalid(invalid);
}
@Override
public boolean isInvalid() {
return super.isInvalidBoolean();
}
@Override
public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) {
super.setRequiredIndicatorVisible(requiredIndicatorVisible);
runBeforeClientResponse(ui -> getElement().callJsFunction(
"$connector.enableClientValidation",
!requiredIndicatorVisible));
}
void runBeforeClientResponse(SerializableConsumer<UI> command) {
getElement().getNode().runWhenAttached(ui -> ui
.beforeClientResponse(this, context -> command.accept(ui)));
}
@Override
public void setClearButtonVisible(boolean clearButtonVisible) {
super.setClearButtonVisible(clearButtonVisible);
}
public boolean isClearButtonVisible() {
return super.isClearButtonVisibleBoolean();
}
@Override
public void setOpened(boolean opened) {
super.setOpened(opened);
}
public boolean isOpened() {
return isOpenedBoolean();
}
@Override
public void setPlaceholder(String placeholder) {
super.setPlaceholder(placeholder);
}
public String getPlaceholder() {
return getPlaceholderString();
}
@Override
public void setPattern(String pattern) {
super.setPattern(pattern);
}
public String getPattern() {
return getPatternString();
}
@Override
public void setDataProvider(DataProvider<T, ?> dataProvider) {
this.dataProvider = dataProvider;
reset();
setupDataProviderListener(dataProvider);
}
private void setupDataProviderListener(DataProvider<T, ?> dataProvider) {
if (dataProviderListenerRegistration != null) {
dataProviderListenerRegistration.remove();
}
dataProviderListenerRegistration = dataProvider
.addDataProviderListener(event -> {
reset();
});
}
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
if (getDataProvider() != null
&& dataProviderListenerRegistration == null) {
setupDataProviderListener(getDataProvider());
}
}
public DataProvider<T, ?> getDataProvider() {
return dataProvider;
}
@Override
protected void onDetach(DetachEvent detachEvent) {
if (dataProviderListenerRegistration != null) {
dataProviderListenerRegistration.remove();
dataProviderListenerRegistration = null;
}
super.onDetach(detachEvent);
}
@Override
public void setAllowCustomValue(boolean allowCustomValue) {
super.setAllowCustomValue(allowCustomValue);
}
public boolean isAllowCustomValue() {
return isAllowCustomValueBoolean();
}
@Override
public Registration addCustomValueSetListener(
ComponentEventListener<CustomValueSetEvent<ComboBoxLight<T>>> listener) {
setAllowCustomValue(true);
customValueListenersCount++;
Registration registration = super.addCustomValueSetListener(listener);
return new CustomValueRegistration(registration);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment