Команды и обработки на формах

Делаем кнопки рабочими: команды формы, их обработчики, вызов сервера по нажатию и обновление формы результатом.

Команда формы — это именованное действие, которое пользователь запускает вручную (обычно кнопкой). У команды есть обработчик — процедура, выполняющаяся при её вызове.

События из прошлого урока платформа запускает сама (открытие, изменение поля). Но многое пользователь должен инициировать осознанно: «Заполнить по шаблону», «Рассчитать скидку», «Отправить на согласование». Для таких действий существуют команды. Команда — это не кнопка. Кнопка — лишь один из способов вызвать команду; та же команда может жить и в командной панели, и в контекстном меню, и вызываться по горячей клавише.

Зачем это нужно на практике

Команды — это «глаголы» вашей формы: всё, что пользователь может сделать по своей воле. Разделение «команда ↔ кнопка» удобно тем, что одно действие можно разместить в нескольких местах интерфейса, не дублируя логику. Написали обработчик один раз — привязали к нему сколько угодно кнопок.

Команда и её обработчик

Команду добавляют в дереве формы (ветка «Команды»), задают имя и указывают процедуру-обработчик. Обработчик команды всегда клиентский и помечается директивой &НаКлиенте — ведь нажатие происходит у пользователя.

&НаКлиенте
Процедура ЗаполнитьТоварыПоОстаткам(Команда)
    // Обработчик команды: параметр "Команда" — сама вызванная команда.
    // Тяжёлую работу (запрос к базе) поручаем серверу.
    ЗаполнитьТоварыПоОстаткамНаСервере();
КонецПроцедуры

Параметр Команда — ссылка на вызванную команду (полезно, если один обработчик обслуживает несколько команд и надо различить, какая нажата, через Команда.Имя).

Кнопка: как команда попадает на экран

Чтобы команда стала доступной, на форму добавляют элемент-кнопку и в её свойстве ИмяКоманды указывают нужную команду. Заголовок кнопки, картинку и расположение настраивают в свойствах элемента. Одну команду можно привязать к нескольким кнопкам сразу.

&НаКлиенте
Процедура ОбновитьКурсВалюты(Команда)
    // Кнопка "Обновить курс" вызывает эту команду.
    // Меняем внешний вид кнопки на время работы — через элемент.
    Элементы.ОбновитьКурс.Доступность = Ложь;
    Попытка
        Объект.Курс = ПолучитьКурсНаДату(Объект.Валюта, Объект.Дата);
    Исключение
        Сообщить("Не удалось получить курс: " + ОписаниеОшибки());
    КонецПопытки;
    Элементы.ОбновитьКурс.Доступность = Истина;
КонецПроцедуры

Вызов серверной процедуры по нажатию

Ключевой паттерн: кнопка нажимается на клиенте, но настоящая работа (запросы, чтение и запись данных) идёт на сервере. Поэтому клиентский обработчик команды почти всегда вызывает серверную процедуру. Обычно берут &НаСервере, когда серверу нужно менять реквизиты формы, и результат сразу окажется на клиенте.

&НаКлиенте
Процедура ЗаполнитьТоварыПоОстаткам(Команда)
    ЗаполнитьТоварыПоОстаткамНаСервере(); // клиент просит сервер
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьТоварыПоОстаткамНаСервере()
    // Серверная процедура с контекстом: видит реквизиты формы (Объект).
    Запрос = Новый Запрос;
    Запрос.Текст =
        "ВЫБРАТЬ
        |    Остатки.Номенклатура КАК Номенклатура,
        |    Остатки.КоличествоОстаток КАК Количество
        |ИЗ
        |    РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки
        |ГДЕ
        |    Остатки.КоличествоОстаток > 0";
    Запрос.УстановитьПараметр("Дата", Объект.Дата);
    Запрос.УстановитьПараметр("Склад", Объект.Склад);

    Объект.Товары.Очистить();
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = Объект.Товары.Добавить();
        НоваяСтрока.Номенклатура = Выборка.Номенклатура;
        НоваяСтрока.Количество   = Выборка.Количество;
    КонецЦикла;
КонецПроцедуры

Здесь серверная процедура прямо меняет табличную часть Объект.Товары. Так как она с контекстом, изменённые реквизиты автоматически вернутся на клиент, и пользователь сразу увидит заполненную таблицу — форма «обновилась» сама.

Передача параметров и обновление формы

Если серверу не нужен весь контекст формы, а только пара значений, эффективнее вызвать &НаСервереБезКонтекста и передать параметры явно, а результат применить на клиенте. Это снижает объём пересылаемых данных.

&НаКлиенте
Процедура РассчитатьСкидку(Команда)
    // Передаём на сервер только сумму, получаем процент скидки.
    Процент = ВычислитьПроцентСкидки(Объект.СуммаДокумента);
    Объект.Скидка = Объект.СуммаДокумента * Процент / 100;
    // Обновляем оформление поля скидки в зависимости от результата.
    Элементы.Скидка.ЦветТекста = ?(Процент > 0, ЦветаСтиля.ПоложительныйТекст, ЦветаСтиля.ОбычныйТекст);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ВычислитьПроцентСкидки(Сумма)
    // Внеконтекстно: контекст формы не пересылается, только параметр "Сумма".
    Если Сумма >= 100000 Тогда
        Возврат 10;
    ИначеЕсли Сумма >= 50000 Тогда
        Возврат 5;
    Иначе
        Возврат 0;
    КонецЕсли;
КонецФункции

Обратите внимание на тернарный оператор ?(Условие, ЗначениеЕслиИстина, ЗначениеЕслиЛожь) — компактный способ выбрать одно из двух значений прямо в выражении.

Как это работает

Нажатие кнопки платформа превращает в вызов связанной команды, а команду — в вызов её обработчика (всегда на клиенте). Дальше клиентский код решает, нужен ли сервер. При контекстном вызове (&НаСервере) платформа упаковывает реквизиты формы, отправляет на сервер, там ваш код их меняет, и обновлённый пакет возвращается на клиент — так форма обновляется. При внеконтекстном вызове пересылаются только явные параметры и возвращаемое значение, а реквизиты формы сервер не видит. Понимание этой «дороги данных» — то, что отличает крепкого разработчика форм от новичка, у которого «кнопка тормозит».

Частые ошибки

  • Писать серверный обработчик команды. Пометить обработчик команды &НаСервере нельзя — он должен быть клиентским. Серверную работу выносят в отдельную процедуру и вызывают из клиентского обработчика.
  • Менять реквизиты формы во внеконтекстном вызове. В &НаСервереБезКонтекста реквизитов формы (в том числе Объект) не видно — присвоение им там невозможно. Нужен контекстный вызов или возврат значения на клиент.
  • Забыть очистить таблицу перед заполнением. Без Объект.Товары.Очистить() команда «Заполнить» будет добавлять строки к уже имеющимся, дублируя данные.
  • Тяжёлые запросы без обратной связи. Долгая команда без блокировки кнопки провоцирует повторные нажатия. Полезно на время работы ставить Элементы.Кнопка.Доступность = Ложь.

Итоги

  • Команда — это действие пользователя; кнопка — лишь способ его вызвать (одну команду можно привязать к нескольким кнопкам).
  • Обработчик команды всегда клиентский (&НаКлиенте) и обычно вызывает серверную процедуру для работы с базой.
  • Контекстный вызов &НаСервере видит и меняет реквизиты формы — форма обновляется автоматически; &НаСервереБезКонтекста быстрее, но работает только через явные параметры.
  • Перед заполнением табличной части её очищают; долгие команды сопровождают блокировкой кнопки и обработкой ошибок через Попытка ... Исключение.
Проверьте себя
1. Какой директивой должен быть помечен обработчик команды формы, привязанной к кнопке?
A&НаСервере
B&НаКлиенте
C&НаСервереБезКонтекста
DДиректива не нужна
2. Команда должна запросом заполнить табличную часть Объект.Товары и сразу показать её пользователю. Какой серверный вызов подходит?
A&НаСервереБезКонтекста — он видит реквизиты формы
B&НаСервере (контекстный) — он видит и меняет Объект.Товары, изменения вернутся на клиент
CНикакой — таблицу заполняют только вручную
D&НаКлиенте — запрос к базе можно выполнить прямо на клиенте