如何使用 vaadin 网格导出到 csv/excel?
how to export to csv/excel using vaadin grids?
在 Vaadin 14+ 中,我正在创建网格并希望用户有一种 stable/easy 方法将网格的内容导出到 csv,或者最好是 Excel。为此,令我感到惊讶的是 Vaadin 似乎没有提供此功能,因此必须使用第 3 方开发人员插件(例如 https://vaadin.com/directory/component/exporter/overview)。但是,这些插件有许多错误(例如,无法将具有日期值的网格正确导出到 Excel 等)。 Vaadin 14 中是否有推荐的方法来支持我认为是任何基于 Web 的网格小部件的高度要求的功能?
无需插件(在 Vaadin 中称为 add-ons)。
DataProvider
您需要了解 Grid
小部件用于 演示 ,而不是数据存储。
每个 Grid
对象都由 DataProvider
, which is responsible for accessing the data store. The data to be displayed in a Grid
might come from some objects in memory, or from a data feed, or from the results of a database query, or from some other source. Following the design principle of separation of concerns 支持,Grid
class 只关心显示数据,不管理数据访问。 DataProvider
界面关注的是管理数据访问,而不是显示数据。所以 Grid
和 DataProvider
一起工作。
对于基于内存的有限数量的数据对象,我们可以使用 DataProvider
的 ListDataProvider
实现。当我们传递数据对象的集合时,可以自动为我们构建此列表数据提供程序。
因此您不从 Grid
对象中导出数据。相反,您想监听 DataProvider
的更改,然后提供通过该数据提供程序获取的导出数据。
DataProvider
中没有内置导出功能。您可以在利用 DataProvider
实现中提供的数据的同时编写自己的导出功能。您可以在许多基于 Java 的库中进行选择,以协助为导出的数据编写数据文件。在下面显示的代码中,我们使用 Apache Commons CSV 库来编写制表符分隔或逗号分隔的值。
这是一个完整的示例应用程序。
我们有一个简单的 Person
class 来保存姓名和 phone 号码。
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
对象的 phone 编号。
我们还有一个导出按钮,它使用 Apache Commons CSV library to write out a CSV file. Notice the key line where we access the data items from the ListDataProvider
. First we cast the data provider to ListDataProvider
, then we extract a Collection
of all the Person
objects stored within. 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
class.
在 Vaadin 14+ 中,我正在创建网格并希望用户有一种 stable/easy 方法将网格的内容导出到 csv,或者最好是 Excel。为此,令我感到惊讶的是 Vaadin 似乎没有提供此功能,因此必须使用第 3 方开发人员插件(例如 https://vaadin.com/directory/component/exporter/overview)。但是,这些插件有许多错误(例如,无法将具有日期值的网格正确导出到 Excel 等)。 Vaadin 14 中是否有推荐的方法来支持我认为是任何基于 Web 的网格小部件的高度要求的功能?
无需插件(在 Vaadin 中称为 add-ons)。
DataProvider
您需要了解 Grid
小部件用于 演示 ,而不是数据存储。
每个 Grid
对象都由 DataProvider
, which is responsible for accessing the data store. The data to be displayed in a Grid
might come from some objects in memory, or from a data feed, or from the results of a database query, or from some other source. Following the design principle of separation of concerns 支持,Grid
class 只关心显示数据,不管理数据访问。 DataProvider
界面关注的是管理数据访问,而不是显示数据。所以 Grid
和 DataProvider
一起工作。
对于基于内存的有限数量的数据对象,我们可以使用 DataProvider
的 ListDataProvider
实现。当我们传递数据对象的集合时,可以自动为我们构建此列表数据提供程序。
因此您不从 Grid
对象中导出数据。相反,您想监听 DataProvider
的更改,然后提供通过该数据提供程序获取的导出数据。
DataProvider
中没有内置导出功能。您可以在利用 DataProvider
实现中提供的数据的同时编写自己的导出功能。您可以在许多基于 Java 的库中进行选择,以协助为导出的数据编写数据文件。在下面显示的代码中,我们使用 Apache Commons CSV 库来编写制表符分隔或逗号分隔的值。
这是一个完整的示例应用程序。
我们有一个简单的 Person
class 来保存姓名和 phone 号码。
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
对象的 phone 编号。
我们还有一个导出按钮,它使用 Apache Commons CSV library to write out a CSV file. Notice the key line where we access the data items from the ListDataProvider
. First we cast the data provider to ListDataProvider
, then we extract a Collection
of all the Person
objects stored within. 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
class.