wbadam / autocomplete-extension

Autocomplete extension add-on for Vaadin 8
https://vaadin.com/directory#!addon/autocomplete-extension
8 stars 7 forks source link

event.getSelectedValue() doesn't get the actual value from the list #9

Closed jonmartinsolaas closed 7 years ago

jonmartinsolaas commented 7 years ago

While the suggest-list may be populated with any object it seems getting the selected value only can return a String? The same string would be available from the TextField. It would be more useful if the getSelectedValue returned the actual value that the selection list was populated with in the first place. Or am I missing something here ... ?

wbadam commented 7 years ago

Please note that the selection event is a feature that is not released yet.

I'm not sure that the text field's value is available in every case on the server when the selection event is fired. TextField has several modes for updating the value on server side (see ValueChangeMode). I still have to test if the value is transferred anyway.

The selection event could perhaps have a getSelectedCaption() method for receiving the information you want. Would that be helpful to you?

Unfortunately there is not much more info on the client that could be provided about the selected item.

jonmartinsolaas commented 7 years ago

Hi, yeah, I figured it would be good to add this before release.

The caption is available as TextField value after select, isn't it?

With SuggestField (another plugin for Vaadin 7) I could do something like this:

    customerSuggestField.addValueChangeListener(e -> {
        SearchResultBean selected = (SearchResultBean) customerSuggestField.getValue();
        if (selected != null) {
            updateCustomerView(selected.id);
        }
    });

eg. the suggestion list would be populated by beans, the caption in the list would be from the toString method, and upon selection, the id of the selected bean would be retrieved and the actual customer data would be retrieved from database, list, or something.

One could easily imagine having a caption made up of customer name and phone-number. But upon selection one would really prefer to get hold of some id of the customer, without having to display that in the caption. Finding the customer by name and phone number would be possible, but not elegant.

wbadam commented 7 years ago

Hi,

I agree that it is a valid use case.

The tricky part is that the extension, for keeping it lightweight, doesn't have access to all the suggestions.

Please take a look in master and see if that is useful to you.

Usage is

extension.addSuggestionSelectListener(event -> {
    event.getSelectedItem().ifPresent(item -> Notification.show(item));
});
jonmartinsolaas commented 7 years ago

Works, and very useful. I'll be using it with hibernate/lucene sarch. Thanks a lot!

jonmartinsolaas commented 7 years ago

It seems, when a broad search term is typed, and the suggestion list is long, it is not always possible to select an item. I suppose this has to do with the comment above about keeping references for up to twice the length of the suggestion list size, to keep things snappy and lightweight.

Would it be possible to make this behaviour use-configurable, so that snappiness is "sacrificed" for the sake of being able to select all items in long suggestion-lists?

My alternative would be to just load everything in a combobox which would be kind of brute-force ...

wbadam commented 7 years ago

What are your settings for the extension, could you paste a short example of your code?

Perhaps the debounce time is set short (time between user typing and suggestion request)?

I think what I should do is to change the current size based item cache to a time based one. Will try to do that later today.

jonmartinsolaas commented 7 years ago

I'll make a short example available. I tried to set the suggestion delay, didn't change anything (except the delay). But increasing suggestionListSize helps. It seems as if the number of actual hits exceeds the suggestionListSize, none of the displayed items in the list are selectable. They should be selectable, and the last line/item in the list could just be a line displaying "Showing XX of total YY hits" or something.

jonmartinsolaas commented 7 years ago

When running the code below and searching for 'Merc', the selection event is logged, when selecting from the list, but the item is not present (or at least not shown in a Notification).

In practice one should type a more narrow criteria, because chances are, the hit the user is looking for isn't in the visible part of the list anyway, but I think the inconsistency is too confusing.

` package com.example;

import com.vaadin.server.VaadinRequest; import com.vaadin.spring.annotation.SpringUI; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Notification; import com.vaadin.ui.TextField; import com.vaadin.ui.UI; import org.vaadin.addons.autocomplete.AutocompleteExtension;

import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;

@SpringUI(path = "/mytest") public class AppUI extends UI {

HorizontalLayout layout = new HorizontalLayout();

TextField search = new TextField("Search");
AutocompleteExtension<SearchBean> extSearch = new AutocompleteExtension<>(search);

@Override
protected void init(VaadinRequest vaadinRequest) {
    layout.setMargin(true);
    layout.setSpacing(true);
    extSearch.setSuggestionGenerator(this::search, this::convertValueBean, this::convertCaptionBean);
    extSearch.addSuggestionSelectListener(event -> {
        System.err.println("Selection event");
        event.getSelectedItem().ifPresent(item -> Notification.show(item.getValue()));
    });
    extSearch.setSuggestionListSize(5);
    layout.addComponent(search);
    setContent(layout);
}

private String convertValueBean(SearchBean customer) {
    return customer.getCaption();
}

// this decides what ends up in the list of suggestions
private String convertCaptionBean(SearchBean customer, String query) {
    return "<div class='suggestion-container'>"
            + "[" + customer.getId() + "] " + customer.getCaption()
            + "</div>";
}

public static class SearchBean {
    private final Integer id;
    private final String value;
    private final String caption;

    public SearchBean(Integer id, String value, String caption) {
        this.id = id;
        this.value = value;
        this.caption = caption;
    }

    public Integer getId() {
        return id;
    }

    public String getValue() {
        return value;
    }

    public String getCaption() {
        return caption;
    }
}

public List<SearchBean> search(String query, int cap) {
    return getData()
            .stream()
            .filter(bean -> bean.getValue().startsWith(query))
            .collect(Collectors.toList());
}

public List<SearchBean> getData() {
    return Arrays.asList(
            new SearchBean(1, "Jupiter the big planet", "Jupiter")
            , new SearchBean(2, "Mars the red planet", "Mars")
            , new SearchBean(3, "Pluto isn't even a proper planet", "Pluto")
            , new SearchBean(4, "Venus, where men aren't from", "Venus")
            , new SearchBean(5, "Venus, Texas", "Venus")
            , new SearchBean(6, "Mercury, the innermost planet", "Mercury")
            , new SearchBean(7, "Earth, home", "Earth")
            , new SearchBean(8, "Ceres, in the asteroid belt", "Ceres")
            , new SearchBean(9, "Mercura, the innermost planet", "Mercurya")
            , new SearchBean(10, "Mercurb, the innermost planet", "Mercuryb")
            , new SearchBean(11, "Mercurc, the innermost planet", "Mercuryc")
            , new SearchBean(12, "Mercurd, the innermost planet", "Mercuryd")
            , new SearchBean(13, "Mercure, the innermost planet", "Mercurye")
            , new SearchBean(14, "Mercurf, the innermost planet", "Mercuryf")
            , new SearchBean(15, "Mercurg, the innermost planet", "Mercuryg")
            , new SearchBean(16, "Mercurh, the innermost planet", "Mercuryh")
            , new SearchBean(17, "Mercuri, the innermost planet", "Mercuryi")
            , new SearchBean(18, "Mercurj, the innermost planet", "Mercuryj")
            , new SearchBean(19, "Mercurk, the innermost planet", "Mercuryk")
            , new SearchBean(20, "Mercurl, the innermost planet", "Mercuryl")
            , new SearchBean(21, "Mercurm, the innermost planet", "Mercurym")
            , new SearchBean(22, "Mercurn, the innermost planet", "Mercuryn")
            , new SearchBean(23, "Mercuro, the innermost planet", "Mercuryo")
            , new SearchBean(24, "Mercurp, the innermost planet", "Mercuryp")

    );
}

} `

I can use this as a workaround, or something similar could be documented as a usage pattern:

public List search(String query, int cap) { List result = getData() .stream() .filter(bean -> bean.getValue().startsWith(query)) .limit(9) .collect(Collectors.toList()); if (result.size() == 9) result.add(new SearchBean(0, "","Only 9 first rows are shown.")); return result; }

wbadam commented 7 years ago

Thanks for the example!

True, I didn't consider the case when the suggested elements exceed the list size.

I have a solution idea, but will have to leave it for tomorrow.

jonmartinsolaas commented 7 years ago

Ok, great, but see last comment in my previous post; I think it would be fair to just document how to avoid the problem, and call it a feature, unless the fix is really simple.

wbadam commented 7 years ago

Hi, the caching of items should now be fixed, please try and let me know if it works for you!

jonmartinsolaas commented 7 years ago

looks much better now, thanks!

wbadam commented 7 years ago

👍 I'll make a release then soon. Just reopen if you find any issues with this feature.