Vaadin 14: изменить размер TextArea после изменения размера окна

с Vaadin 14.1.19 и проектом Vaadin «Мой стартовый проект» я попытался создать многострочный TextArea. На первый взгляд он работает нормально, но при изменении размера TextArea количество строк не адаптируется. Это мой код:

package com.packagename.myapp;
import org.springframework.beans.factory.annotation.Autowired;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;
@Route(layout = DesktopLayout.class)
@PWA(name = "Project Base for Vaadin Flow with Spring", shortName = "Project Base")
public class MainView extends VerticalLayout {
    public MainView(@Autowired MessageBean bean) {
        String loremIpsum = "Lorem ipsum dolor sit amet, [....].";
        TextArea readOnlyTA = new TextArea();
        readOnlyTA.setLabel("Read-only");
        readOnlyTA.setWidth("1500px");
        readOnlyTA.setMaxWidth("80vw");
        readOnlyTA.setValue(loremIpsum);
        readOnlyTA.setReadOnly(true);
        add(readOnlyTA);

        TextArea readWriteTA = new TextArea();
        readWriteTA.setLabel("normal");
        readWriteTA.setWidth("1500px");
        readWriteTA.setMaxWidth("80vw");
        readWriteTA.setValue(loremIpsum);
        add(readWriteTA);

        Div readOnlyDiv = new Div();
        readOnlyDiv.setWidth("1500px");
        readOnlyDiv.setMaxWidth("80vw");
        readOnlyDiv.add(loremIpsum);
        add(readOnlyDiv);
    }
}

Когда я открываю вид с широким окном, он выглядит так, и это нормально:

Vaadin 14.1.19 TextArea с большим количеством текста на широком экране

Когда я изменяю размер окна, оно выглядит так, что это не нормально, потому что читается только начало текста. Я даже не могу прокручивать TextAreas.

Vaadin 14.1.19 TextArea с большим количеством текста на маленьком экране

Только размер DIV изменяется так, как я ожидал.

Как я могу изменить размер TextArea Vaadin при изменении размера окна?


person S. Doe    schedule 13.03.2020    source источник
comment
Кажется, что Vaadin пересчитывает высоту при каждом изменении значения в текстовой области, доступной для записи: когда я что-то набираю, текстовая область, доступная для записи, просто получает правильную высоту, непосредственно как свойство CSS в атрибуте стиля, например. высота: 110 пикселей.   -  person S. Doe    schedule 13.03.2020


Ответы (1)


Кажется, это ошибка. Я только что сообщил об этом здесь https://github.com/vaadin/vaadin-text-field/issues/460

В качестве обходного пути вы можете установить прослушиватель окна resize, который запускает textArea._updateHeight(); на стороне клиента, чтобы инициировать обновление высоты. Или вы можете использовать ResizeObserver, но он все еще несколько ограничен поддержка браузера

Вот наивный пример добавления обходного пути прослушивателя изменения размера в Flow:

textArea.getElement().executeJs(
    "window.addEventListener('resize', function() { $0._updateHeight(); });",
    textArea.getElement());

Но вы должны обернуть этот обработчик изменения размера в какой-нибудь базовый дебаунсер, чтобы он не запускался _updateHeight() слишком часто во время изменения размера, чтобы избежать проблем с производительностью.

В качестве альтернативы вы можете сделать это:

UI.getCurrent().getPage().addBrowserWindowResizeListener(event -> {
    textArea.getElement().executeJs("this._updateHeight();");
});

Я не уверен, есть ли у addBrowserWindowResizeListener какая-то встроенная защита от дребезга или нет, но я думаю, что это, по крайней мере, вызовет обращения сервера туда и обратно при изменении размера, чего, вероятно, следует избегать, если это возможно.

Изменить:

Вот более универсальный подход, создающий новый компонент, который расширяет Vaadin TextArea и использует ResizeObserver в поддерживаемых браузерах (с setInterval в качестве запасного варианта для других браузеров).

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.textfield.TextArea;

public class CustomTextArea extends TextArea {
    private boolean initDone = false;

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        if (!initDone) {
            // debounce method borrowed from: https://davidwalsh.name/essential-javascript-functions
            getElement().executeJs(
                    "const debounce = function(func, wait, immediate) {" +
                    "  var timeout;" +
                    "  return function() {" +
                    "    var context = this, args = arguments;" +
                    "    var later = function() {" +
                    "      timeout = null;" +
                    "      if (!immediate) func.apply(context, args);" +
                    "    };" +
                    "    var callNow = immediate && !timeout;" +
                    "    clearTimeout(timeout);" +
                    "    timeout = setTimeout(later, wait);" +
                    "    if (callNow) func.apply(context, args);" +
                    "  };" +
                    "};" +
                    "const textArea = $0;" +
                    "const updateTextAreaHeight = function() { textArea._updateHeight(); };" +
                    "const debounceTimeout = 50;" +
                    "const intervalTimeout = 500;" +
                    "" +
                    "if (window.ResizeObserver) {" +
                    "  let textAreaDebouncer;" +
                    "  const resizeObserver = new ResizeObserver(debounce(updateTextAreaHeight, debounceTimeout));" +
                    "  resizeObserver.observe(textArea);" +
                    "} else {" +
                    "  let textAreaWidth = textArea.clientWidth;" +
                    "  window.setInterval(function() {" +
                    "    if (textAreaWidth !== textArea.clientWidth) {" +
                    "      updateTextAreaHeight();" +
                    "      textAreaWidth = textArea.clientWidth;" +
                    "    }" +
                    "  }, intervalTimeout);" +
                    "}", getElement());
            initDone = true;
        }
    }
}

Тогда просто используйте этот CustomTextArea вместо TextArea и он должен работать.

person Haprog    schedule 19.03.2020
comment
Привет, Haprog, WindowResizeListener кажется лишь частью универсального решения. Изменение размера поля ввода/текстовой области также может произойти, когда поле помещено в разделенный макет: vaadin .com/components/vaadin-split-layout - person S. Doe; 19.03.2020
comment
Это правда. ResizeObserver для компонента был бы оптимальным решением, но до тех пор, пока у него не будет более широкой поддержки браузера, вам, вероятно, придется использовать разные решения, основанные на контексте, в зависимости от того, какие события могут вызвать изменение размера ширины компонента. Изменение размера окна, вероятно, является наиболее распространенным случаем, который легко прослушать, но если ширина компонента может измениться без изменения размера окна, вам нужен другой триггер, основанный на вашем контексте. - person Haprog; 19.03.2020
comment
С SplitLayout вы можете использовать addSplitterDragendListener() или связанное с ним событие splitter-dragend в элементе разделителя на стороне клиента. - person Haprog; 19.03.2020
comment
В качестве универсального решения вы можете использовать некоторый полифил ResizeObserver на стороне клиента или вернуться к опросу ширины компонента с некоторым интервалом (чтобы проверить, изменилась ли ширина) в браузерах, которые не поддерживают ResizeObserver. - person Haprog; 19.03.2020
comment
@S.Doe Я отредактировал свой ответ и добавил пример того, как расширить TextArea, чтобы вы могли легко использовать пользовательскую версию (со встроенным исправлением/обходным решением) где угодно. - person Haprog; 19.03.2020
comment
Успешно протестирован с Chromium 80.0.3987.132 и Firefox 74.0. С изменением размера окна и перемещением резака в SplitLayout. Здорово. Спасибо! :-) - person S. Doe; 19.03.2020