警告:
我在这件事上不是专家。我在这里提供的示例代码似乎运行正常。通过研究有限的文档并阅读了网络上的许多其他文章,我将这种解决方案拼凑在一起。矿山可能不是最好的解决方案。
有关更多信息,请参见Vaadin手册的“
动态内容” 页面。
我们在您的问题中包含三个主要部分:
- Vaadin Web应用程序页面上的小部件,可为用户提供下载。
- 动态内容创建者
- 在用户计算机上创建的文件的默认名称
我对前两个有解决方案,但对第三个没有解决方案。
下载小工具
如课题中所述,我们确实使用了
Anchor小部件(请参阅Javadoc)。
我们在布局上定义一个成员变量。
private Anchor anchor;
我们通过传递一个
StreamResource对象来实例化。此类在Vaadin中定义。它的工作是包装我们制作的类,该类将产生扩展Java类的实现
InputStream。
输入流通过从其
read方法返回
int其值是预期的八位位组的数字,即0-255,一次提供一个八位位组的数据。到达数据末尾时,传回负数
read。
在我们的代码中,我们实现了
makeStreamOfContent一种充当
InputStream工厂的方法。
private InputStream makeInputStreamOfContent ( ){ return GenerativeInputStream.make( 4 );}在实例化我们的时
StreamResource,我们传递了一个引用该方法的方法引用
makeInputStreamOfContent。由于没有输入流或任何数据尚未生成,因此我们在这里变得有点抽象。我们只是在准备舞台;该动作稍后发生。
传递给的第一个参数
newStreamResource是要在用户的客户端计算机上创建的文件的默认名称。在此示例中,我们使用的虚构名称
report.text。
anchor = new Anchor( new StreamResource( "report.text" , this :: makeInputStreamOfContent ) , "Download generated content" );
接下来,我们
download在HTML5
anchor元素上设置属性。此属性向浏览器指示当用户单击链接时我们打算下载目标。
anchor.getElement().setAttribute( "download" , true );
您可以通过将锚小部件包装在内来显示图标
Button。
downloadButton = new Button( new Icon( VaadinIcon.DOWNLOAD_ALT ) );anchor.add( downloadButton );
如果使用这样的图标,则应从小
Anchor部件中删除文本标签。而是将任何所需的文本放在中
Button。因此,我们将空字符串(
"")
newAnchor传递给,并将标签文本作为第一个参数传递给
new Button。
anchor = new Anchor( new StreamResource( "report.text" , this :: makeInputStreamOfContent ) , "" );anchor.getElement().setAttribute( "download" , true );downloadButton = new Button( "Download generated content" , new Icon( VaadinIcon.DOWNLOAD_ALT ) );anchor.add( downloadButton );
动态内容创建者
我们需要实现一个
InputStream子类,以提供给我们的下载小部件。
该
InputStream抽象类提供了所有,但其中的一个方法的实现。我们只需要实现
read满足项目需求的方法即可。
这是一种可能的实现方式。实例化
GenerativeInputStream对象时,传递要生成的行数。一次生成一行数据,然后逐个八位字节地将数据提供给客户端。完成该行后,将生成另一行。因此,我们一次只处理一行就可以节省内存。
馈送到客户端的八位位组是构成我们行的UTF-8文本的八位位组。预期文本的每个字符都可以包含一个或多个八位字节。如果您不理解这一点,请阅读Joel
Spolsky撰写的有趣且内容丰富的文章,
《绝对最低限度每个软件开发人员绝对,肯定必须了解Unipre和字符集》(无借口!) 。
package work.basil.example;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.nio.charset.Charset;import java.time.Instant;import java.util.Objects;import java.util.Optional;import java.util.UUID;import java.util.function.IntSupplier;// Generates random data on-the-fly, to simulate generating a report in a business app.//// The data is delivered to the calling program as an `InputStream`. Data is generated// one line (row) at a time. After a line is exhausted (has been delivered octet by octet// to the client web browser), the next line is generated. This approach conserves memory// without materializing the entire data set into RAM all at once.//// By Basil Bourque. Use at your own risk.// © 2020 Basil Bourque. This source pre may be used by others agreeing to the terms of the ISC License.// https://en.wikipedia.org/wiki/ISC_licensepublic class GenerativeInputStream extends InputStream{ private int rowsLimit, nthRow; InputStream rowInputStream; private IntSupplier supplier; static private String DELIMITER = "t"; static private String END_OF_LINE = "n"; static private int END_OF_DATA = - 1; // --------| Constructors | ------------------- private GenerativeInputStream ( int countRows ) { this.rowsLimit = countRows; this.nthRow = 0; supplier = ( ) -> this.provideNextInt(); } // --------| Static Factory | ------------------- static public GenerativeInputStream make ( int countRows ) { var gis = new GenerativeInputStream( countRows ); gis.rowInputStream = gis.nextRowInputStream().orElseThrow(); return gis; } private int provideNextInt ( ) { int result = END_OF_DATA; if ( Objects.isNull( this.rowInputStream ) ) { result = END_OF_DATA; // Should not reach this point, as we checked for null in the factory method and would have thrown an exception there. } else // Else the row input stream is *not* null, so read next octet. { try { result = rowInputStream.read(); // If that row has exhausted all its octets, move on to the next row. if ( result == END_OF_DATA ) { Optional < InputStream > optionalInputStream = this.nextRowInputStream(); if ( optionalInputStream.isEmpty() ) // Receiving an empty optional for the input stream of a row means we have exhausted all the rows. { result = END_OF_DATA; // Signal that we are done providing data. } else { rowInputStream = optionalInputStream.get(); result = rowInputStream.read(); } } } catch ( IOException e ) { e.printStackTrace(); } } return result; } private Optional < InputStream > nextRowInputStream ( ) { Optional < String > row = this.nextRow(); // If we have no more rows, signal the end of data feed with an empty optional. if ( row.isEmpty() ) { return Optional.empty(); } else { InputStream inputStream = new ByteArrayInputStream( row.get().getBytes( Charset.forName( "UTF-8" ) ) ); return Optional.of( inputStream ); } } private Optional < String > nextRow ( ) { if ( nthRow <= rowsLimit ) // If we have another row to give, give it. { nthRow++; String rowString = UUID.randomUUID() + DELIMITER + Instant.now().toString() + END_OF_LINE; return Optional.of( rowString ); } else // Else we have exhausted the rows. So return empty Optional as a signal. { return Optional.empty(); } } // --------| `InputStream` | ------------------- @Override public int read ( ) throws IOException { return this.provideNextInt(); }}默认文件名
我找不到完成最后一部分的方法,默认情况下文件的名称包含生成内容的时间。
我什至在这一点上发布了有关堆栈溢出的问题: 在Vaadin
Flow应用程序中使用文件名默认为用户事件的日期时间进行下载
问题在于,在加载页面并
Anchor实例化该窗口小部件时,链接窗口小部件背后的URL仅创建一次。之后,在用户阅读页面时,时间流逝。当用户最终单击链接以开始下载时,当前时刻晚于URL中记录的时刻。
似乎没有简单的方法可以将该URL更新到用户的click事件或download事件的当前时刻。
提示
顺便说一下,对于实际工作,我不会使用自己的代码构建导出的行。相反,我将使用 Apache Commons
CSV之
类的库来编写制表符分隔或逗号分隔值(CSV)的内容。
资源资源
- 论坛: Vaadin 10让用户下载文件
- 论坛: 字节数组中的图像
- 手册: 动态内容



