Предостережение: я не эксперт в этом вопросе. Представленный здесь пример кода работает правильно. Я собрал это решение, изучив ограниченную документацию и прочитав множество других сообщений в Интернете. Моя может быть не лучшим решением.
Для получения дополнительной информации см. Динамическое содержимое em > страница руководства Vaadin.
В вашем вопросе есть три основных элемента:
- Виджет на странице веб-приложения Vaadin, предлагающий пользователю загрузку.
- Создатель динамического контента
- Имя по умолчанию для файла, создаваемого на компьютере пользователя.
У меня есть решение первых двух, но нет третьего.
Скачать виджет
Как упоминалось в вопросе, мы используем виджет Anchor
(см. Документ Javadoc).
Мы определяем переменную-член в нашем макете.
private Anchor anchor;
Мы создаем экземпляр, передавая StreamResource
объект. Этот класс определен в Vaadin. Его задача здесь - обернуть созданный нами класс, который создаст реализацию, расширяющую класс Java _ 4_.
Входной поток предоставляет данные по одному октету за раз, возвращая из своего read
метода значение int
, значение которого является числовым номером предполагаемого октета, 0–255. При достижении конца данных read
возвращает отрицательное значение.
В нашем коде мы реализовали метод makeStreamOfContent
, который действует как InputStream
фабрика.
private InputStream makeInputStreamOfContent ( )
{
return GenerativeInputStream.make( 4 );
}
При создании нашего StreamResource
мы передаем ссылку на этот makeInputStreamOfContent
метод. Здесь мы немного отвлечемся, так как ни входной поток, ни какие-либо данные еще не генерируются. Мы только подготавливаем почву; действие происходит позже.
Первый аргумент, переданный в new StreamResource
, - это имя по умолчанию для файла, который будет создан на клиентской машине пользователя. В этом примере мы используем простое имя report.text
.
anchor =
new Anchor(
new StreamResource( "report.text" , this :: makeInputStreamOfContent ) ,
"Download generated content"
)
;
Затем мы устанавливаем атрибут download
на HTML5 anchor
элемент. Этот атрибут указывает браузеру, что мы намерены загружать целевой объект, когда пользователь щелкает ссылку.
anchor.getElement().setAttribute( "download" , true );
Вы можете отобразить значок, заключив виджет привязки внутрь _ 19_.
downloadButton = new Button( new Icon( VaadinIcon.DOWNLOAD_ALT ) );
anchor.add( downloadButton );
Если вы используете такой значок, вы должны удалить текстовую метку с виджета Anchor
. Вместо этого поместите любой желаемый текст в Button
. Таким образом, мы передадим пустую строку (""
) в new Anchor
и передадим текст метки в качестве первого аргумента 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
предоставляет реализации всех своих методов, кроме одного. Нам нужно реализовать только _ 29_, чтобы удовлетворить потребности нашего проекта.
Вот одна из возможных таких реализаций. Когда вы создаете экземпляр объекта GenerativeInputStream
, передайте количество строк, которое вы хотите сгенерировать. Данные генерируются по одной строке за раз, а затем октет за октетом передаются клиенту. Когда закончите с этой строкой, будет создана другая строка. Таким образом, мы экономим память, работая только с одной строкой за раз.
Октеты, передаваемые клиенту, представляют собой октеты, составляющие текст UTF-8 нашего ряд. Каждый символ предполагаемого текста может состоять из одного или нескольких октетов. Если вы этого не понимаете, прочтите занимательный и информативный пост Абсолютный минимум, который должен знать каждый разработчик программного обеспечения о Unicode и наборах символов (без оправданий!) Джоэла Спольски.
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 code may be used by others agreeing to the terms of the ISC License.
// https://en.wikipedia.org/wiki/ISC_license
public 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 приложение
Проблема в том, что URL-адрес за виджетом ссылки создается один раз, когда страница была загружена и этот виджет Anchor
был создан. После этого, пока пользователь читает страницу, проходит время. Когда пользователь в конце концов щелкает ссылку, чтобы начать загрузку, текущий момент наступает позже, чем момент, записанный в URL-адресе.
Кажется, нет простого способа обновить этот URL до текущего момента события щелчка пользователя или события загрузки.
подсказки
Кстати, для реальной работы я бы не собирал экспортируемые строки своим собственным кодом. Вместо этого я бы использовал такую библиотеку, как Apache Commons CSV a> для записи с разделением табуляцией или Значения, разделенные запятыми (CSV) содержание.
Ресурсы
person
Basil Bourque
schedule
23.03.2020