как экспортировать в csv / excel с помощью сетки vaadin?

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


person Jonathan Sylvester    schedule 07.03.2020    source источник


Ответы (1)


Нет необходимости в надстройках (называемых надстройками в Vaadin).

DataProvider

Вы должны понимать, что Grid < / a> предназначен для презентации, а не для хранения данных.

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

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

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

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

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

введите здесь описание изображения

У нас есть простой 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 предлагает несколько разновидностей форматов файлов. Обычно лучшим будет стандартный формат, определенный в RFC 4180. Но вы упомянули Microsoft Excel, для которого библиотека поддерживает этот вариант. См. Класс CSVFormat.

person Basil Bourque    schedule 08.03.2020
comment
Я отметил ваш ответ как правильный. Это потрясающе. Один вопрос: этот код создаст файл, который хранится на сервере. Как нажатие кнопки экспорта может вместо этого инициировать загрузку на локальный компьютер пользователя? (В идеале он предлагал бы пользователю выбрать сохранение в качестве местоположения, чтобы пользователь мог выбрать местоположение и имя файла.) 2-й бонус: может ли это создание файла + загрузка происходить непосредственно в потоковом режиме, чтобы файл никогда не создавался на сервере, но вместо этого передается непосредственно на загрузку пользователя? Это будет иметь значение в первую очередь для больших файлов.) - person Jonathan Sylvester; 12.03.2020
comment
@JonathanSylvester Чтобы сохранить узкую направленность, поскольку вопросы и ответы должны быть на Stack Overflow, я опубликовал и ответил на другой такой вопрос: Динамически- созданный контент для загрузки без записи файла на стороне сервера в веб-приложении Vaadin Flow - person Basil Bourque; 24.03.2020
comment
Если это поможет, вот видео об этом: youtube.com/watch?v=HgY -Sgf_iVI - person Alejandro Duarte; 31.03.2021