Buscar este blog

Imágenes del tema: MichaelJay. Con la tecnología de Blogger.

Author Info

Labels

Video Of Day

Flickr

Mostrando entradas con la etiqueta VAADIN. Mostrar todas las entradas
Mostrando entradas con la etiqueta VAADIN. Mostrar todas las entradas

viernes, 6 de abril de 2018

ComboBox lazy loading with REST API in Vaadin 8

In this post we'll explore how to access a REST service in order to display items in a ComboBox in Vaadin 8.
First, we'll implement a REST service with Spring Boot, as described here
The web service will be exposed at http://127.0.0.1:8081/api/countries. It will receive no parameters and return an array containing the name of all the countries known by the JVM (the Geography knowledge of the JVM is amazing!).
@RestController
public class CountriesController {

    @RequestMapping("/api/countries")
    public String[] getCountries() {
        return countries().toArray(String[]::new);
    }    

    private static Stream<String> getCountries() {
        return Stream.of(Locale.getISOCountries())
            .map(countryCode -> new Locale("", countryCode))
            .map(locale->locale.getDisplayCountry(Locale.ENGLISH))
            .sorted();
    }
}
Next, we'll implement a REST client that consumes our REST API
public class CountriesClient {        

    private static String ALL_COUNTRIES_URI = "http://127.0.0.1:8081/api/countries";

    public Stream<String> getAllCountries() {
        RestTemplate restTemplate = new RestTemplate();
        String[] countries = restTemplate.getForObject(ALL_COUNTRIES_URI, String[].class);
        return Stream.of(countries);
    }
}
We'll need to add the dependencies for spring-web and jackson-databind in the project's POM (for a detailed explanation of consuming REST services with Spring, you can check this tutorial https://spring.io/guides/gs/consuming-rest/)
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.4</version>
    </dependency>
Now, we'll write a small application that loads the countries form the REST client, and displays them in a combobox:
    @Theme(ValoTheme.THEME_NAME)
    public class MyUI extends UI {

        private CountriesClient client = new CountriesClient();

        @Override
        protected void init(VaadinRequest vaadinRequest) {
            VerticalLayout layout = new VerticalLayout();
            ComboBox<String> cbCountry = new ComboBox<>();
            cbCountry.setWidth(300, Unit.PIXELS);
            initializeItems(cbCountry);

            layout.addComponents(cbCountry);
            setContent(layout);
        }

        private void initializeItems(ComboBox<String> cbCountry) {
            cbCountry.setItems(client.getAllCountries());
        }

        @WebServlet(urlPatterns = "/simple", asyncSupported = true)
        @VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
        public static class MyUIServlet extends VaadinServlet {
        }

    }
While this approach works, there is a drawback: all the items are loaded when the UI initializes, and setItems instantiates a ListDataProvider, which stores all the items in memory until the combobox itself is disposed. (This is not so critical in this case, since there are only 250 countries, but imagine a combobox loaded with thousands of items...)
Fortunately, we can do it better by specifying an smarter DataProvider that will take the responsability of fetching items on demand. For this we'll use the method setDataProvider of ComboBox, that instead of a stream/collection/array of items, receives as parameter a DataProvider<T, String>.
DataProvider<T,F> is an interface where the type parameter T refers to the data type (in this case, the ComboBox<T> item type), and the second parameter is the filter type (which for ComboBox is always String, since the ComboBox is filtered by the text the user writes inside). The Vaadin framework supplies an implementation of DataProvider, named CallbackDataProvider, which has the following constructor:
public CallbackDataProvider(
        FetchCallback<T, F> fetchCallback,
        CountCallback<T, F> countCallback)
The fetch callback returns a stream with the items matching a Query, and the count callback returns the number of items matching a Query (a Query is another object in the Vaadin framework, that contains information about index, limits, sorting and filtering):
    @FunctionalInterface
    public interface FetchCallback<T, F> extends Serializable {
        public Stream<T> fetch(Query<T, F> query);
    }

    @FunctionalInterface
    public interface CountCallback<T, F> extends Serializable {
        public int count(Query<T, F> query);
    }

    public class Query<T, F> implements Serializable {
        private final int offset;
        private final int limit;
        private final List<QuerySortOrder> sortOrders;
        private final Comparator<T> inMemorySorting;
        private final F filter;
        //...
    }
In order to take advantage from the CallbackDataProvider, we'll need to introduce a couple of changes to our REST service (CountriesController).
First, we'll need a method that returns a subset of <count> items, matching a given <filter> and starting at a given <offset>.
    @RequestMapping("/api/countries/list")
    public String[] getCountries(
            @RequestParam(value="filter") String filter,
            @RequestParam(value="offset") int offset, 
            @RequestParam(value="limit")  int count) {
        return countries().filter(country->filter(country,filter))
            .skip(offset).limit(count).toArray(String[]::new);
    }       

    private boolean filter(String country, String filter) {
        return filter.isEmpty() || country.toLowerCase().contains(filter.toLowerCase());
    }
Then, we'll need a method that returns the count of items matching a given <filter>
    @RequestMapping("/api/countries/count")
    public int getCountries(
            @RequestParam(value="filter") String filter) {
        return (int) countries().filter(country->filter(country,filter)).count();
    }
Now, we proceed to adapt the REST client (CountriesClient) to these changes, by adding the following methods:
    private static String GET_COUNTRIES_URI = "http://127.0.0.1:8081/api/countries/list?offset={1}&limit={2}&filter={3}";

    private static String COUNT_URI = "http://127.0.0.1:8081/api/countries/count?filter={1}";

    public Stream<String> getCountries(int offset, int limit, String filter) {
        RestTemplate restTemplate = new RestTemplate();
        String[] countries = restTemplate.getForObject(GET_COUNTRIES_URI, String[].class, offset, limit, filter);
        return Stream.of(countries);
    }

    public int getCount(String filter) {
        RestTemplate restTemplate = new RestTemplate();
        Integer count = restTemplate.getForObject(COUNT_URI, Integer.class, filter);
        return count;
    }
Finally, we integrate the modified service in the UI code:
private void initializeItems(ComboBox<String> cbCountry) {
    cbCountry.setDataProvider(new CallbackDataProvider<>(
        query-> 
            client.getCountries(query.getOffset(),query.getLimit(), getFilter(query)),
        query->
            (int) client.getCount(getFilter(query))
        ));
    }

    private String getFilter(Query<?,String> query) {
        return ((String)query.getFilter().orElse(null));
    }
As a final note, there are other components that also use DataProvider, such as Grid and TwinColSelect.
You can download and run the complete code of this example from github.

martes, 6 de junio de 2017

Vaadin Grid editing capabilities

This time I'd like to talk a little bit about something that I believe is one of the most powerful capabilities of Vaadin Grid component: the editing feature. The default view of the component is a read-only view, of the tabular data loaded in the component. But if you enable the editor, you can visually edit the grid's cells and change the data that the grid is holding in the server's memory. Let's dig in some difficulties to use this feature and how to overcome them.

Inline editing

If you look at the demo sampler application, you'll see that the Item Editor, is like a little form that opens whenever you double click a row. That's different than the previous component in older Vaadin versions (Table). With Table, when you double click on it, the entire row turn into read/write mode, without changing the row into a "mini form" as grid does now. Of course this behavior is configurable, you can disable it just by calling setBuffered(false).
Despite of being somehow more visual, the new conduct, could be an undesirable behavior for enterprise applications that happens to use the keyboard a lot, and not depending so much on the mouse, to handle the table or grid.
If your users desire that kind of grid management, then the Grid Fast Navigation Addon is for you. It uses the Component Extension feature, to give the grid a new look, that makes the editing much more easier, and transforms it in a sort of excel style facility.

Grid Fast Navigation Addon in action

Components

Another thing that prevented some developers to stop using Table and starting to use grid, was that you couldn't use a standard component inside a cell of a Grid. If you wanted to, for example, use checkboxes inside a cell, you had to create a special class, called "renderer", that should handle the server and the client side of that cell.
Luckily there are some addons that may help you with that task. The one that stands out is the Grid renderes collection (it exists for Vaadin 7 and 8 versions). It provides a lot of components that let you handle select, checkbox, images, etc.
In the last version of Vaadin, one of the most interesting features, is a new Renderer, that adds the capability to render a Vaadin component inside a cell of a grid. You can see a preview in the sampler application.
If you are using Vaadin 7, although, you could use a experimental version of that feature, installing the Component Renderer Addon.
Happy coding!



Interested for our works and services?
Get more of our update !