Как экспортировать в CSV / Excel с помощью Vaadin сетки? - PullRequest
2 голосов
/ 07 марта 2020

В Vaadin 14+ я создаю сетки и хотел бы, чтобы у пользователей был стабильный / простой способ экспорта содержимого сетки в csv или, предпочтительно, в Excel. Для этого я был удивлен, что Vaadin, по-видимому, не предоставляет эту функциональность, поэтому приходится использовать сторонние плагины разработчика (такие как https://vaadin.com/directory/component/exporter/overview). Однако эти плагины имеют множество ошибок (например, не могут правильно экспортировать сетки со значениями даты в Excel et c.). Есть ли в Vaadin 14 рекомендуемый подход к поддержке, как я полагаю, крайне востребованной функции любого веб-виджета сетки?

1 Ответ

4 голосов
/ 09 марта 2020

Нет необходимости для плагинов (называемых дополнениями в Vaadin).

DataProvider

Необходимо понимать, что виджет Grid предназначен для представления , а не для хранения данных.

Каждый объект Grid поддерживается DataProvider, который отвечает за доступ к хранилищу данных. Данные, которые должны отображаться в Grid, могут поступать из некоторых объектов в памяти, или из фида данных, или из результатов запроса к базе данных, или из какого-либо другого источника. Следуя принципу разделения интересов , класс Grid занимается только отображением данных, а не управлением доступом к данным. Интерфейс DataProvider связан с управлением доступом к данным, а не с отображением данных. Так что Grid и DataProvider работают вместе.

Для ограниченного числа объектов данных, основанных все в памяти, мы можем использовать ListDataProvider реализацию DataProvider. Этот поставщик данных списка может быть создан автоматически для нас, когда мы передаем коллекцию наших объектов данных.

Таким образом, вы не экспортируете данные из объекта Grid. Вместо этого вы хотите прослушать изменения DataProvider, а затем предложить экспортировать данные, полученные через этого поставщика данных.

В DataProvider нет встроенной функции экспорта. Вы можете написать свою собственную функцию экспорта, опираясь на данные, доступные в реализации DataProvider. Вы можете выбирать из множества библиотек Java, чтобы помочь в записи файлов данных для ваших экспортируемых данных. В приведенном ниже коде мы используем библиотеку Apache Commons CSV для записи значений, разделенных табуляцией или разделенных запятыми.

Вот пример приложения.

enter image description here

У нас есть простой Person класс для хранения имени и номера телефона.

package work.basil.example;

import java.util.Objects;

public class Person
{
    //---------------|  Member vars  |--------------------------------
    private String name, phone;


    //---------------|  Constructors  |--------------------------------

    public Person ( String name , String phone )
    {
        this.name = name;
        this.phone = phone;
    }


    //---------------|  Accessors  |--------------------------------

    public String getName ( ) { return this.name; }

    public void setName ( String name ) { this.name = name; }

    public String getPhone ( ) { return this.phone; }

    public void setPhone ( String phone ) { this.phone = phone; }


    //---------------|  Object  |--------------------------------


    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        Person person = ( Person ) o;
        return getName().equals( person.getName() );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash( getName() );
    }
}

Это все приложение Vaadin 14.1.18, которое генерирует 4 Person объектов в качестве примера набора данных. Эти объекты подаются в Grid, который производит ListDataProvider для нашего удобства.

У нас есть текстовое поле для редактирования номера телефона выбранного Person объекта, представленного в сетке.

И у нас есть кнопка экспорта, которая использует Apache Commons Библиотека CSV для записи файла CSV . Обратите внимание на ключевую строку, где мы получаем доступ к элементам данных из ListDataProvider. Сначала мы приводим поставщика данных к ListDataProvider, затем извлекаем Collection из всех Person объектов, хранящихся в нем. Java Generics обеспечивает безопасность типов и дает возможность компилятору знать, что поставщик данных содержит Person объектов.

Collection < Person > persons = ( ( ListDataProvider < Person > ) grid.getDataProvider() ).getItems();

Далее следует полный код приложения Vaadin 14.1.

package work.basil.example;

import com.vaadin.flow.component.AbstractField;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridSingleSelectionModel;
import com.vaadin.flow.component.html.Input;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * The main view contains a button and a click listener.
 */
@Route ( "" )
//@PWA ( name = "Project Base for Vaadin", shortName = "Project Base" )
@CssImport ( "./styles/shared-styles.css" )
@CssImport ( value = "./styles/vaadin-text-field-styles.css", themeFor = "vaadin-text-field" )
public class MainView extends VerticalLayout
{

    Grid < Person > grid;
    TextField phoneField;
    Button phoneSaveButton, exportButton;

    public MainView ( )
    {
        // Widgets
        List < Person > personList = new ArrayList <>( 4 );
        personList.add( new Person( "Alice" , "555.123.1234" ) );
        personList.add( new Person( "Bob" , "555.688.4787" ) );
        personList.add( new Person( "Carol" , "555.632.2664" ) );
        personList.add( new Person( "David" , "555.543.2323" ) );

        // Create a grid bound to the list
        grid = new Grid <>();
        grid.setItems( personList );
        grid.addColumn( Person :: getName ).setHeader( "Name" );
        grid.addColumn( Person :: getPhone ).setHeader( "Phone" );
        GridSingleSelectionModel < Person > singleSelect = ( GridSingleSelectionModel < Person > ) grid.getSelectionModel();
        singleSelect.setDeselectAllowed( false );
        singleSelect.addSingleSelectionListener( singleSelectionEvent -> {
                    Optional < Person > personOptional = singleSelectionEvent.getSelectedItem();
                    if ( personOptional.isPresent() )
                    {
                        this.phoneField.setValue( personOptional.get().getPhone() );
                    }
                }
        );

        phoneField = new TextField( "Phone:" );

        phoneSaveButton = new Button( "Update phone on person " );
        phoneSaveButton.addClickListener(
                ( ClickEvent < Button > clickEvent ) -> {
                    Optional < Person > personOptional = ( ( GridSingleSelectionModel < Person > ) grid.getSelectionModel() ).getSelectedItem();
                    if ( personOptional.isEmpty() )
                    {
                        Notification.show( "First, select a person in list." );
                    } else
                    {
                        Person person = personOptional.get();
                        person.setPhone( phoneField.getValue() );
                        grid.getDataProvider().refreshItem( person );
                    }
                }
        );

        exportButton = new Button( "Export" );
        exportButton.setEnabled( false );
        exportButton.addClickListener(
                ( ClickEvent < Button > clickEvent ) -> {
                    String fileName = "Persons_" + Instant.now().toString() + ".csv";
                    final String fileNamePath = "/Users/basilbourque/" + fileName;
                    try (
                            BufferedWriter writer = Files.newBufferedWriter( Paths.get( fileNamePath ) ) ;
                            CSVPrinter csvPrinter = new CSVPrinter( writer , CSVFormat.RFC4180.withHeader( "Name" , "Phone" ) ) ;
                    )
                    {
                        Collection < Person > persons = ( ( ListDataProvider < Person > ) grid.getDataProvider() ).getItems();
                        for ( Person person : persons )
                        {
                            csvPrinter.printRecord( person.getName() , person.getPhone() );
                        }
                    }
                    catch ( IOException e )
                    {
                        e.printStackTrace();
                    }

                    // Tell user.
                    Notification.show( "Exported to file in your home folder: " + fileName );
                }
        );
        grid.getDataProvider().addDataProviderListener( dataChangeEvent -> {
            exportButton.setEnabled( true );
        } );


        // Arrange
        this.add( grid , phoneField , phoneSaveButton , exportButton );
    }
}

Кстати, Apache Commons CSV предлагает несколько разновидностей форматов файлов. Обычно лучшим вариантом будет стандартный формат, определенный в RF C 4180. Но вы упомянули Microsoft Excel, для которого библиотека поддерживает этот вариант. См. CSVFormat класс.

...