问题
In Vaadin 14+, I'm creating grids and would like the users to have a stable/easy way to export the grid's contents to csv or, preferablly, Excel. To do so, I was surprised that Vaadin does not appear to provide this functionality, and so one has to use 3rd party developer plugins (such as https://vaadin.com/directory/component/exporter/overview). However, these plugins have numerous bugs (eg can't export grids with date values correctly to Excel etc.). Is there a recommended approach in Vaadin 14 to supporting what I would assume is a highly requested feature of any web-based grid widget?
回答1:
No need for plugins (called add-ons in Vaadin).
DataProvider
You need to understand that a Grid widget is for presentation, not data storage.
Each Grid
object is backed by a 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, the Grid
class is concerned only with displaying data, not managing data access. The DataProvider
interface is concerned with managing data access, not displaying data. So Grid
and DataProvider
work together.
For a limited number of data objects based all in memory, we can use a ListDataProvider implementation of DataProvider
. This list data provider can be built automatically for us when we pass a collection of our data objects.
So you do not export data from the Grid
object. Instead, you want to listen for changes to the DataProvider
, and then offer exporting data obtained through that data provider.
There is no built-in export feature in DataProvider
. You can write your own export feature while drawing upon the data made available in the DataProvider
implementation. You can choose between many Java-based libraries to assist in writing data files for your exported data. In the code shown below, we use the Apache Commons CSV library for writing tab-delimited or comma-separated values.
Here is a complete example app.
We have a simple Person
class to hold a name and a phone number.
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() );
}
}
This is an entire Vaadin 14.1.18 app that generates 4 Person
objects as a sample data set. Those objects are fed to a Grid
, which produces a ListDataProvider
for our convenience.
We have a text field for editing the phone number of the selected Person
object represented in the grid.
And we have an export button which uses the 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 provides type-safety, and empowers the compiler to know that the data provider contains Person
objects.
Collection < Person > persons = ( ( ListDataProvider < Person > ) grid.getDataProvider() ).getItems();
Complete Vaadin 14.1 app code follows.
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 );
}
}
By the way, Apache Commons CSV offers several varieties of file formats. Usually, the best would be the standard format defined in RFC 4180. But you mentioned Microsoft Excel, for which the library supports that variant. See the CSVFormat class.
来源:https://stackoverflow.com/questions/60581348/how-to-export-to-csv-excel-using-vaadin-grids