Быстрое переключение между записями

Добрый день, @alex2910sk!

К сожалению, без кода контроллера экрана и XML дескриптора очень сложно ответить на ваш вопрос и помочь вам. Вы могли бы их прислать?

С уважением,
Глеб

Добрый день.
Я создал тестовый класс.
Его реализация аналогичная вашей за исключением того, что в нем переопределен один метод initEditComponents (делает поля редактируемыми сразу), и есть 2 листнера:

ExpenseLineBrowseTest

package com.company.itam.web.screens.expenseline;

import com.haulmont.cuba.gui.ComponentsHelper;
import com.haulmont.cuba.gui.components.ComponentContainer;
import com.haulmont.cuba.gui.components.Form;
import com.haulmont.cuba.gui.components.TabSheet;
import com.haulmont.cuba.gui.components.Table;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.screen.*;
import com.company.itam.entity.ExpenseLine;

@UiController("itam_ExpenseLine.browseTest")
@UiDescriptor("expense-line-browse-test.xml")
@LookupComponent("table")
@LoadDataBeforeShow
public class ExpenseLineBrowseTest extends MasterDetailScreen<ExpenseLine> {
    @Override
    protected void initEditComponents(boolean enabled) {
        TabSheet tabSheet = getTabSheet();
        if (tabSheet != null) {
            ComponentsHelper.walkComponents(tabSheet, (component, name) -> {
                if (component instanceof Form) {
                    ((Form) component).setEditable(enabled);
                } else if (component instanceof Table) {
                    ((Table) component).getActions().forEach(action -> action.setEnabled(enabled));
                } else if (!(component instanceof ComponentContainer)) {
                    component.setEnabled(enabled);
                }
            });
        }
        getForm().setEditable(true);
        getLookupBox().setEnabled(true);
        editing = true;
    }

    @Subscribe(id = "expenseLineDc", target = Target.DATA_CONTAINER)
    public void onExpenseLineDcItemPropertyChange(InstanceContainer.ItemPropertyChangeEvent<ExpenseLine> event) {
        System.out.println();
    }

    @Subscribe(id = "expenseLineDc", target = Target.DATA_CONTAINER)
    public void onExpenseLineDcItemChange(InstanceContainer.ItemChangeEvent<ExpenseLine> event) {

    }



}

И его дескриптор:

<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        xmlns:c="http://schemas.haulmont.com/cuba/screen/jpql_condition.xsd"
        caption="msg://browseCaption"
        focusComponent="table"
        messagesPack="com.company.itam.web.screens.expenseline">
    <data>
        <collection id="expenseLinesDc"
                    class="com.company.itam.entity.ExpenseLine"
                    view="expenseLine-browse">
            <loader id="expenseLinesDl">
                <query>
                    <![CDATA[select e from itam_ExpenseLine e]]>
                </query>
            </loader>
        </collection>
        <instance id="expenseLineDc"
                  class="com.company.itam.entity.ExpenseLine"
                  view="expenseLine-edit">
            <loader/>
        </instance>
    </data>
    <actions>
        <action id="save" icon="icons/ok.png" caption="mainMsg://actions.Ok" shortcut="CTRL-ENTER"/>
        <action id="cancel" icon="icons/cancel.png" caption="mainMsg://actions.Cancel" description="Esc"/>
    </actions>
    <dialogMode height="600" width="800"/>
    <layout>
        <split id="split" height="100%" orientation="vertical" reversePosition="true" width="100%">
            <vbox id="lookupBox" expand="table" height="100%" margin="false,true,false,false" spacing="true">
                <filter id="filter" applyTo="table" dataLoader="expenseLinesDl">
                    <properties include=".*"/>
                </filter>
                <groupTable id="table"
                            width="100%"
                            dataContainer="expenseLinesDc">
                    <actions>
                        <action id="create" type="create"/>
                        <action id="edit" type="edit"/>
                        <action id="remove" type="remove"/>
                    </actions>
                    <columns>
                        <column id="code"/>
                        <column id="moneyValue"/>
                        <column id="expenseDate"/>
<!--                        <column id="lnkCostCenter"/>-->
<!--                        <column id="lnkPortfolio"/>-->
<!--                        <column id="expenseType"/>-->
                        <column id="name"/>
                    </columns>
                    <rowsCount/>
                    <buttonsPanel id="buttonsPanel"
                                  alwaysVisible="true">
                        <button id="createBtn" action="table.create"/>
                        <button id="editBtn" action="table.edit"/>
                        <button id="removeBtn" action="table.remove"/>
                    </buttonsPanel>
                </groupTable>
                <hbox id="lookupActions" spacing="true" visible="false">
                    <button action="lookupSelectAction"/>
                    <button action="lookupCancelAction"/>
                </hbox>
            </vbox>
            <vbox id="editBox" height="100%" margin="false,false,false,true" expand="fieldGroupBox" spacing="true">
                <scrollBox id="fieldGroupBox">
                    <form id="form" dataContainer="expenseLineDc">
                        <column width="250px">
                            <textField id="codeField" property="code"/>
                            <textField id="moneyValueField" property="moneyValue"/>
                            <dateField id="expenseDateField" property="expenseDate"/>
<!--                            <pickerField id="lnkCostCenterField" property="lnkCostCenter">-->
<!--                                <actions>-->
<!--                                    <action id="lookup" type="picker_lookup"/>-->
<!--                                    <action id="clear" type="picker_clear"/>-->
<!--                                </actions>-->
<!--                            </pickerField>-->
<!--                            <pickerField id="lnkPortfolioField" property="lnkPortfolio">-->
<!--                                <actions>-->
<!--                                    <action id="lookup" type="picker_lookup"/>-->
<!--                                    <action id="clear" type="picker_clear"/>-->
<!--                                </actions>-->
<!--                            </pickerField>-->
<!--                            <lookupField id="expenseTypeField" property="expenseType"/>-->
                            <textField id="nameField" property="name"/>
                        </column>
                    </form>
                </scrollBox>
                <hbox id="actionsPane" spacing="true" visible="false">
                    <button id="saveBtn" action="save"/>
                    <button id="cancelBtn" action="cancel"/>
                </hbox>
            </vbox>
        </split>
    </layout>
</window>

Чем больше полей, тем быстрее вызывается баг.
Но можно вызвать и с текущим набором.
image

Параллельно хочу уточнить, есть ли у системы возможность делать экран нередактируемым, пока он не прогрузит все данные до конца. Например мы выбираем запись, Cuba какое-то время крутит колесо загрузки по центру, пока выполняет свой код и ожидает окончания RPC взаимодействий, а потом отпускает весь экран, после чего пользователь опять может взаимодействовать с UI. Это бы помогло решить проблему.
Блокировка Vaadin сессии не помогает, так как UI все равно доступен для нажатий.
Через листнеры экрана сделать также не получается, так как они отрабатывают один за другим, и только после полной отработки отдают изменения на фронт.

Глеб, Добрый день.
Приложил ответы.
Возможно, не ответил на ваши сообщение напрямую, поэтому боюсь, что вы могли их не заметить раньше.

Добрый день, @alex2910sk!

Прошу прощения за долгий ответ. У меня получилось воспроизвести вашу проблему.

Как показано на данном скриншоте, для полей с типом DateTime меняется только значение дня (DateField), а время (TimeField) остается прежним.

Возможно проблема заключается в компоненте Vaadin. Rpc метод для смены значения даты в DateField компоненте помечен аннотацией com.vaadin.shared.annotations.@Delayed (см. com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc.#updateValueWithDelay). Это означает, что событие смены значения в DateField отправляется на сервер не сразу после изменения значения, а оно помещается в очередь и отправляется на сервер при вызове любого другого rpc метода, не помеченного аннотацией @Delayed.

А так как для отображения значений дат используется один и тот же компонент DateField, то возникает ситуация, когда вы уже сменили элемент в instance контейнере, а значение даты меняется в зависимости от rpc вызовов. Поэтому значение дня остается от старого элемента, а время от нового.

С уважением,
Глеб

Добрый день, Глеб.

Спасибо! Можете подсказать, можно ли с этим элементом что-то сделать?

Добрый день, @alex2910sk !

Вы можете воспользоваться стандартной реализацией MasterDetailScreen. Так как объект будет заблокирован для изменения при смене значений в главной коллекции, то описанная выше проблема не будет возникать.

С уважением,
Глеб

Других вариантов нет, как я понимаю?

Можете попробовать переопределить метод CubaDateFieldWidget#buildDate(), чтобы вызывать rpc метод без аннотации @Delayed.

    @Override
    public void buildDate() {
        // Save previous value
        String previousValue = getText();
        super.buildDate();

        // Restore previous value if the input could not be parsed
        if (!parsable) {
            setText(previousValue);
        }
        updateTextFieldEnabled();
        bufferedDateString = text.getText();
        updateBufferedResolutions();
        // send the Time changes.
        sendBufferedValues();

        updateTextState();
    }

Других решений я, к сожалению, не могу предложить.

С уважением,
Глеб

Глеб, спасибо!
Попробуем.
Подскажите, пожалуйста, мне переопределенный класс как-то нужно пометить? (аннотацией, или внести в какой-нибудь XML)

Добрый день, @alex2910sk!

Вам нужно выполнить несколько шагов, чтобы переопределить метод в виджете:

  1. Добавить web-toolkit модуль
  2. Создать наследника CubaDateFieldWidget, в котором переопределить метод buildDate()
package com.company.sample.web.toolkit.ui.customdatefield;

import com.haulmont.cuba.web.widgets.client.datefield.CubaDateFieldWidget;

public class CustomCubaDateFieldWidget extends CubaDateFieldWidget {

    @Override
    public void buildDate() {
        // Save previous value
        String previousValue = getText();
        super.buildDate();

        // Restore previous value if the input could not be parsed
        if (!parsable) {
            setText(previousValue);
        }
        updateTextFieldEnabled();
        bufferedDateString = text.getText();
        updateBufferedResolutions();
        // send the Time changes.
        sendBufferedValues();

        updateTextState();
    }
}
  1. Заменить CubaDateFieldWidgetна CustomCubaDateFieldWidget в AppWidgetSet.gwt.xml
<?xml version="1.0" encoding="UTF-8"?>
<module>

    <inherits name="com.haulmont.cuba.web.widgets.WidgetSet"/>

    <replace-with class="com.company.sample.web.toolkit.ui.customdatefield.CustomCubaDateFieldWidget">
        <when-type-is class="com.haulmont.cuba.web.widgets.client.datefield.CubaDateFieldWidget"/>
    </replace-with>
</module>

С уважением,
Глеб

1 симпатия

Глеб, Добрый день!

Наконец выбил время, чтобы проверить текущий вариант.
У меня возникает проблема при попытке собрать модуль web-toolkit (пустой, только что добавленный).
Пробовал менять его название, сокращать путь к конечному пакету с классами, но ничего не получается.

Подскажите, может сталкивались, как решить эту проблему?
image

Добрый день, @alex2910sk!

Попробуйте решение, описанное в следующем тикете.
Также можете попробовать мигрировать проект на gradle 6.1, там данная проблема исправлена.
Как временное решение можете попробовать сократить путь до jre от корня, сократить путь до gradle от корня или создать системную переменную GRADLE_USER_HOME C:\gradle-caches.

С уважением,
Глеб

1 симпатия

Добрый день!
Попробовал установить новую версию Gradle, через несколько устраненных ошибок запнулся на еще одной:
image

Пробовал переустанавливать JDK, делал ребилд с включенной настройкой чистки кеша в Idea, но пока ничего не помогает.
Причем запинается опять на этом же таске.
Подскажите, есть ли решение у этой проблемы…?

Подскажите, пожалуйста, в чем здесь может быть беда?

Добрый день,
В документацию добавлено подробное описание, как можно исключить ненужные для web-toolkit зависимости и избавиться от “error=206” без обновления версии Gradle:

https://doc.cuba-platform.com/manual-7.2-ru/widgetset_win_path_too_long.html

1 симпатия

Добрый день.
Я поэтапно проделала все манипуляции, описанные в инструкции под версией gradle 5.6.4-all.
Сначала я наблюдал ошибку 206, но после исключения транзитивных зависимостей модуля web-toolkit 206 ошибка пропала, теперь у меня возникает эксепшен при сборке:
Caused by: org.gradle.process.internal.ExecException: Process 'command 'C:\Program Files\Java\jdk1.8.0_261\bin\java.exe'' finished with non-zero exit value 1
Этот же эксепшен у меня возникал и ранее после того, когда я попытался собрать версию под gradle 6.1-all и gradle 6.6.1-all. При этом 206 ошибка не возникала.

Без установленного модуля web-toolkit ошибок не возникает.

Приложил ответ. Кстати, почему то когда я отвечаю на чей-то ответ у меня не всегда отображается вот это
image .
И поэтому я не до конца понимаю, получили ли вы мой ответ, или он просто прикрепился к посту без уведомления и прочего…

В том сообщении, которое вы копируете, нет никаких деталей и по нему невозможно ничего посоветовать.

Запускайте сборку в консоли с флагом --stacktrace:

gradlew deploy --stacktrace > build.log

Тогда будет детальное сообщение об ошибке (почему java.exe не запустилась), которое можно будет разбирать.

Для версии 5.6.4:

Task :app-core:assembleDbScripts UP-TO-DATE
Task :app-core:dbScriptsArchive UP-TO-DATE
Task :app-core:beansXml UP-TO-DATE
Task :app-global:beansXml UP-TO-DATE
Task :app-global:processResources UP-TO-DATE
Task :app-global:buildInfo UP-TO-DATE
Task :app-global:compileJava UP-TO-DATE
Task :app-global:classes UP-TO-DATE
Task :app-global:jar UP-TO-DATE
Task :app-core:compileJava UP-TO-DATE
Task :app-core:processResources UP-TO-DATE
Task :app-core:classes UP-TO-DATE
Task :app-core:jar UP-TO-DATE
Task :app-core:sourceJar UP-TO-DATE
Task :app-core:assemble UP-TO-DATE
Task :app-core:cleanConf UP-TO-DATE
Task :app-core:deploy
Task :app-web:buildScssThemes UP-TO-DATE
Task :app-web:beansXml UP-TO-DATE
Task :app-web:compileJava UP-TO-DATE
Task :app-web:processResources UP-TO-DATE
Task :app-web:classes UP-TO-DATE
Task :app-web:jar UP-TO-DATE
Task :app-web:sourceJar UP-TO-DATE
Task :app-web:themesJar UP-TO-DATE
Task :app-web:webArchive UP-TO-DATE
Task :app-web:assemble UP-TO-DATE
Task :app-web:cleanConf UP-TO-DATE
Task :app-web:deploy
Task :app-web-toolkit:compileJava NO-SOURCE
Task :app-web-toolkit:processResources UP-TO-DATE
Task :app-web-toolkit:classes UP-TO-DATE
Task :app-web-toolkit:buildWidgetSet
Loading inherited module ‘com.company.itam.web.toolkit.ui.AppWidgetSet’
Loading inherited module ‘com.haulmont.charts.web.widgets.ChartsWidgetSet’
[ERROR] Unable to find ‘com/haulmont/charts/web/widgets/ChartsWidgetSet.gwt.xml’ on your classpath; could be a typo, or maybe you forgot to include a classpath entry for source?
Task :app-web-toolkit:buildWidgetSet FAILED
Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
Use ‘–warning-mode all’ to show the individual deprecation warnings.
See https://docs.gradle.org/5.6.4/userguide/command_line_interface.html#sec:command_line_warnings
26 actionable tasks: 3 executed, 23 up-to-date

Для версии 6.6.1:

Task :app-core:assembleDbScripts UP-TO-DATE
Task :app-core:dbScriptsArchive UP-TO-DATE
Task :app-core:beansXml UP-TO-DATE
Task :app-global:beansXml UP-TO-DATE
Task :app-global:processResources UP-TO-DATE
Task :app-global:buildInfo UP-TO-DATE
Task :app-global:compileJava UP-TO-DATE
Task :app-global:classes UP-TO-DATE
Task :app-global:jar UP-TO-DATE
Task :app-core:compileJava UP-TO-DATE
Task :app-core:processResources UP-TO-DATE
Task :app-core:classes UP-TO-DATE
Task :app-core:jar UP-TO-DATE
Task :app-core:sourceJar UP-TO-DATE
Task :app-core:assemble UP-TO-DATE
Task :app-core:cleanConf UP-TO-DATE
Task :app-core:deploy
Task :app-web:buildScssThemes UP-TO-DATE
Task :app-web:beansXml UP-TO-DATE
Task :app-web:compileJava UP-TO-DATE
Task :app-web:processResources UP-TO-DATE
Task :app-web:classes UP-TO-DATE
Task :app-web:jar UP-TO-DATE
Task :app-web:sourceJar UP-TO-DATE
Task :app-web:themesJar UP-TO-DATE
Task :app-web:webArchive UP-TO-DATE
Task :app-web:assemble UP-TO-DATE
Task :app-web:cleanConf UP-TO-DATE
Task :app-web:deploy
Task :app-web-toolkit:compileJava NO-SOURCE
Task :app-web-toolkit:processResources UP-TO-DATE
Task :app-web-toolkit:classes UP-TO-DATE
Task :app-web-toolkit:buildWidgetSet FAILED
Compiling module com.company.itam.web.toolkit.ui.AppWidgetSet
[ERROR] Hint: Check that your module inherits ‘com.google.gwt.core.Core’ either directly or indirectly (most often by inheriting module ‘com.google.gwt.user.User’)
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use ‘–warning-mode all’ to show the individual deprecation warnings.
See https://docs.gradle.org/6.6.1/userguide/command_line_interface.html#sec:command_line_warnings
26 actionable tasks: 3 executed, 23 up-to-date

Для версии 6.6.1 я включил следующие наследование в AppWidgetSet.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>
<module>

    <inherits name="com.google.gwt.core.Core" />
    <inherits name="com.google.gwt.user.User" />
    <inherits name="com.haulmont.cuba.web.widgets.WidgetSet" />
    <inherits name="com.haulmont.charts.web.widgets.ChartsWidgetSet" />
</module>

Ошибка сохраняется.

Сможете помочь с одной из вышеперечисленных?