Как отловить переключение на замещаемого пользователя в главном окне приложения?


(Kirill Khoroshilov) #1

В проекте доработано главное меню. Добавлен свой UI-элемент. При открытии главного окна приложения происходит инициализация данного UI - элемента. При открытии второй и последующих вкладок (и (в) других браузерах) происходит проверка. В них логика инициализации UI -элемента игнорируется, до тех пор пока открыта “первая вкладка” (UI).

Элемент инициализируется для определенного пользователя системы и поэтому переход под замещаемого должен отменять инициализацию UI-элемента.

Как отловить смену пользователя у UserIndicator? ValueChangeListener на него не повесить так как он HorizontalLayout.

У действия ChangeSubstUserAction есть метод пустышка doAfterChangeUser(), но как до него достучаться?


(Юрий Артамонов) #3

Здравствуйте,

На самом деле, это очень сложный вопрос.

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

Отдельная сложность состоит в том, что при замещении пользователя, UI вкладки полностью перестраивается с нуля и поэтому слушатель будет выполняться в условиях, когда сессия уже обновлена, а UI перестроен.

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

Если событие замещения нужно обработать в UI контроллере, то можно добавить UserSubstitutionListener для объекта Connection, например в контроллере главного экрана:

import com.haulmont.cuba.web.Connection;

...
    @Inject
    protected Connection connection;
    
    @Override
    public void init(Map<String, Object> params) {
        super.init(params);

        connection.addUserSubstitutionListener(event -> {
            showNotification("Substituted");
        });

Важный момент, что эти слушатели удаляются после выполнения выхода из приложения.

Ну и последняя опция - расширить UI компонент UserIndicator или даже разработать свой, ведь он устроен совсем не сложно. Механизм расширения UI компонентов в платформе позволяет переопределять стандартные компоненты своими наследниками.

Может быть вы поделитесь своей задачей? Я не уверен, что вполне понимаю ваш сценарий использования.


(Kirill Khoroshilov) #4

Юрий, спасибо за ответ!

Идея с сonnection скорее всего подойдет. Попробую. Отпишу в посте.
Перегрузить UserIndicator пока не могу. Есть ограничение которое, не позволяет этого сделать в конкретно разрабатываемом проекте.


(Kirill Khoroshilov) #5

Продолжение истории…
I.

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

После экспериментов понял что при закрытии вкладки браузера можно запустить код. Согласен с Юрием что этот метод не гарантирует 100% надежности.

Итак разбираемся:
При открытии нового окна браузера с приложением/первой вкладки в уже запущенном браузере платформа создает новый экземпляр App. App в свою очередь инициализирует новый экземпляр AppUI, а этот в свою очередь AppWindow и далее MainWindow. У объекта App можно получить коллекцию AppUI’s через метод getAppUIs().
При открытии второй (и последующих) вкладки(-ок) с приложением в инициализированном App создается второй AppUI -> AppWindow -> MainWindow. Если же открыть второй браузер (другой) то инициализируется второй App со своей цепочкой AppUI -> AppWindow -> MainWindow. Если нужно более подробно то лучше изучить документацию VAADIN FRAMEWORK 8.

Что нужно сделать что бы отловить закрытие AppUI (вкладки браузера):

  1. Создаем наследника AppUI
public class MyAppUI extends AppUI
  1. Перегружаем метод init
@Override
    protected void init(VaadinRequest request) {
        super.init(request);

        JavaScript.getCurrent().addFunction("closeUI", new JavaScriptFunction() {
            @Override
            public void call(JsonArray arguments) {
                <some code>
            }
        });

        String func = "window.onbeforeunload = function handleWindowClose(event) {closeUI(); return null;}";
        Page.getCurrent().getJavaScript().execute(func);
    }

Что тут происходит.

  1. Мы добавляем на страницу JavaScriptFunction которая вызывает код на серверной стороне через RPC. Желательно что вызываемый код не был длительной операцией. Хотя запуская в отладке и ставя точку останова получал эффект что вкладка уже закрылась, а код еще не выполнен.

Кстати это то место в которое можно написать следующий метод

@Override
public void call(JsonArray arguments) {
    MyAppUI.this.close(); 
}

таким образом гарантированно скажем приложению что вкладка закрыта.
2. Добавляем callback на событие закрытия вкладки который выполняет первую JS функцию. И возвращает null. Если хотим что бы браузер выводил сообщение о подтверждении действия выхода то функция должна возвращать пустую строку, хотя некоторым браузерам можно прокидывать и сообщение.

Вот собственно и все. Возможно это костыль или велосипед, но выглядит элегантно. Проверено в Chrome(Opera) и Mozilla.

II. Отловить переключение удалось как и советовал Юрий с помощью UserSubstitutionListener без необходимости переделывать UserIndicator.


(Юрий Артамонов) #6

После экспериментов понял что данное утверждение Юрия (по отношению к CUBA - VAADIN) ошибочно и при закрытии вкладки браузера можно запустить код.

Представьте, что на момент закрытия вкладки у пользователя не было сети, на 1 секунду. Никакие события до сервер-сайда не дойдут. Если вы говорите о способе с синхронным AJAX, то напомню, синхронные AJAX запросы deprecated во всех веб-браузерах.

Проблема фундаментальная и надёжного способа нет: https://stackoverflow.com/questions/6162188/javascript-browsers-window-close-send-an-ajax-request-or-run-a-script-on-win


(Юрий Артамонов) #8

Понимаете, в вашем решении есть надежда, что запрос по сети успеет отправиться пока вкладка закрывается. Гарантий, что вкладка будет закрыта на сервере это не даёт. Разверните ваше приложение на удалённом сервере и вы заметите, что половина ваших запросов не успевает отправиться при закрытии вкладки.


(Kirill Khoroshilov) #9

Юрий!

Согласен что это вполне не надежное решение и возможно стоить посмотреть и в сторону navigator.sendBeacon.
В отлове закрывающейся вкладки много всяких сценариев. Но для меня важно отловить намеренное закрытие вкладки на системе работающей в рамках одного здания (одной сети) с минимальным риском потери связи с сервером.
Не пишу что это 100% решение для всех случаев, но для частного сценария может подойти.