.. _g3_spec_server_user_activity_events:
События активности пользователя в форме
==========================================
:term:`Прикладной код` может узнать, что пользователь перестал работать в :term:`форме <Форма>` и
что он к ней вернулся.
Активность отслеживается отдельно для главной формы приложения и каждой :term:`MDI`-формы.
У модальных форм и лукапов своего отсчёта нет. Работа в них продлевает активность формы, из
которой они открыты.
.. versionadded:: 1.29.0-ms5
Включение и время бездействия
-------------------------------
Механизм включается :term:`метаданными <Метаданные>`. Форма отслеживается, если у представления
её главной :term:`выборки <Выборка>` задан атрибут :xsd:attr:`View.Representation.userIdleTimeout`.
Настраивать сервер для этого не нужно.
.. code-block:: xml
Атрибут задаёт время бездействия в секундах. Значение 0 отключает отслеживание формы. Форма без
атрибута тоже не отслеживается. Значение читается один раз при открытии формы и дальше не
меняется.
Общее время бездействия для многих форм базы задают умолчанием в конфигурации сервера, секцией
:xsd:class:`Configuration.Databases.Database.UserActivity`:
.. code-block:: xml
Умолчание применяется к формам, у которых атрибут в представлении главной выборки не задан.
Значение атрибута перекрывает умолчание. Если секции нет, нет и умолчания, но формы со своим
атрибутом отслеживаются всё равно. Конфигурация перечитывается только при перезапуске сервера.
Если ни одна открытая форма не отслеживается, механизм не расходует ресурсы сервера.
.. note::
Не путайте с :xsd:attr:`Configuration.SessionPool.idleClientTimeout`. Тот механизм решает
задачу безопасности: он отключает бездействующего клиента от сессии, см.
:ref:`spec_server_security_idleclienttimeout`. Здесь клиент остаётся подключённым, а
прикладной код получает уведомление.
Прикладные события
--------------------
:term:`События <Событие>` приходят в главную выборку формы без аргументов:
.. list-table::
:header-rows: 1
* - Что произошло
- Событие
- Обработчик в :term:`Avi`-классе
* - Пользователь перестал работать в форме
- :btk:event:`form.UserIdle`
- ``onUserIdle()``
* - Пользователь вернулся к работе в форме
- :btk:event:`form.UserActive`
- ``onUserActive()``
:term:`Обработчики <Обработчик события>` объявляются в Avi-классе главной выборки формы методами
с аннотацией ``@Event``. Если обработчика нет, форма событие пропускает. Это не ошибка.
Что считается активностью в форме
-----------------------------------
На этой странице *командой* называется сообщение, которое клиент отправляет серверу, когда
пользователь выполняет действие в форме. Это не то же самое, что команда браузерного
:term:`плагина <Плагин>`.
Активность формы — это любая команда этой формы или открытой из неё модальной формы, лукапа или
диалога. Примеры действий, которые отправляют команду:
- нажатие кнопки, пункта меню или горячей клавиши;
- переход по записям списка, разворачивание узла дерева, сортировка, применение фильтра;
- редактирование значения в поле, вставка и удаление записи, сохранение и отмена;
- работа в модальной форме, открытой из этой формы, и ответ на её диалог.
Активностью **не** считается:
- автоматическое обновление данных по таймеру: пользователь в нём не участвует;
- действия вне форм: работа с панелью уведомлений и закрытие приложения;
- движение мыши, прокрутка и набор текста в поле ввода: команд они не отправляют.
Неочевидные случаи
--------------------
**Набор текста и прокрутка списка.** Пока пользователь набирает значение в поле, команда не
отправляется, и долгий набор выглядит как простой. То же с прокруткой уже загруженной части
списка. Активность засчитается, когда пользователь закончит ввод значения или прокрутит список
за пределы загруженной части.
**Открытый диалог.** Ожидание ответа на диалог активностью не считается. Забытый на ночь диалог
даст ``UserIdle`` по истечении времени бездействия. Ответ на диалог — активность: форма получит
``UserActive``, и только потом продолжится прерванная операция. Если ожидание закончилось без
ответа, по таймауту или из-за разрыва связи, активности нет: пользователь к форме не вернулся.
**Долгая команда.** Пока сервер выполняет команду формы, ``UserIdle`` не придёт. Время
бездействия отсчитывается после завершения команды.
**Невыполненная команда.** Если сервер не выполнил команду, потому что форма к этому моменту
уже изменилась, активность всё равно продлевается: пользователь действие совершил.
**Закрытие формы в простое.** Закрытие начинается такой же командой, как любая другая. Поэтому
форма сначала получит ``UserActive``, а затем :btk:event:`beforeCloseForm`. Если обработчик
``UserActive`` захватывает ресурс, ресурс будет захвачен и тут же отпущен. Сервер не может
заранее знать, что команда закроет форму, поэтому обработчик должен быть к этому готов, см.
:ref:`g3_spec_server_user_activity_events_recommendations`.
**Разрыв связи с клиентом.** Отключение клиента отсчёт не останавливает: форма сессии, от которой
отключился клиент, тоже должна освободить занятые ресурсы. Отслеживание прекращается, когда
закрывается форма или сессия.
Когда и в каком порядке приходят события
------------------------------------------
1. ``UserIdle`` отправляется, когда простой формы достиг времени бездействия. Событие может
опоздать: сервер проверяет формы периодически. Период проверки равен половине наименьшего
времени бездействия среди открытых отслеживаемых форм, но не меньше одной и не больше
тридцати секунд. Опоздание не превышает этого периода.
2. ``UserActive`` отправляется до того, как сервер выполнит команду, которая его вызвала. Если
пользователь вернулся, ответив на диалог, событие отправляется до продолжения прерванной
операции.
3. События строго чередуются: ``UserActive`` приходит только после ``UserIdle``, дублей нет
ни в ту, ни в другую сторону. Начальное состояние формы — активна.
4. События приходят, пока закрытие формы ещё можно отменить: результатом
:btk:event:`onCloseFormQuery`, отказом пользователя сохранять данные или ошибкой обработчика
:btk:event:`beforeCloseForm`. Форма, закрытие которой отменили, работает дальше как обычно.
Когда закрытие стало окончательным, события прекращаются. Ресурсы, освобождённые в простое,
остаются освобождёнными, окончательную очистку выполняет обработчик
:btk:event:`beforeCloseForm`. Если этот обработчик показал диалог и пользователь долго не
отвечает, форма может получить ``UserIdle`` во время закрытия.
5. Исключение обработчика записывается в журнал сервера и дальше не передаётся. Ошибка в
обработчике ``UserActive`` не отменяет команду. Ошибка в обработчике ``UserIdle`` не мешает
проверке остальных форм, повторно событие не отправляется.
.. _g3_spec_server_user_activity_events_recommendations:
Рекомендации прикладному разработчику
---------------------------------------
Один из вариантов использования — на время простоя освобождать занятые формой ресурсы,
например прикладные блокировки, и захватывать их заново, когда пользователь вернётся к работе.
Обработчики должны быть готовы к повторному вызову: событие может прийти, когда ресурс уже
освобождён или уже захвачен. ``UserActive`` должен переживать и случай, когда освобождённый
ресурс за время простоя занял другой пользователь. Неудачный захват — нормальный сценарий, его
нужно обработать.
Готового признака «форма в простое» механизм не даёт. Если он нужен прикладному коду, его ведут
в обработчиках: события чередуются без дублей, поэтому состояние всегда известно.
.. seealso::
:btk:event:`form.UserIdle`
:btk:event:`form.UserActive`
:ref:`spec_server_security_idleclienttimeout`
:xsd:class:`Configuration.Databases.Database.UserActivity`
:xsd:attr:`View.Representation.userIdleTimeout`