Оглавление:
- Reactive Declarative State Model
- Основные архитектурные слои (паттерн MVC)
- Работа с таблицами на форме
- Внедрение подсистемы в форму
- Описание модели объекта
- Практикум
- Демо Заявка на операцию
- Демо Звездный рейтинг
- Состав и установка - Состав - Установка
- Зависимости
Reactive Declarative State Model
RDSM

Рисунок 1 — Схема паттерна Reactive Declarative State Model
Паттерн Reactive Declarative UI State Model позволяет:
- Упростить код формы, устранив явные обработчики ПриИзменении
- Обеспечить консистентность — зависимости всегда рассчитываются корректно
- Повторно использовать модель для расчета значений как в UI, так и без UI
- Инкапсулировать логику расчёта в одном месте
Упрощение кода формы
В стандартном подходе к программированию формы необходимо обеспечить нужное поведение объекта и формы через обработчики событий: При изменении, Перед началом выбора и т.д. В подходе через RDUI поведение элементов формы задано декларативно. Для каждого элемента описываются формулы вычисления свойств или задана функция состояния элемента. Все необходимые обработчики элемента подсистема сама подключает к предопределенным процедурам, обеспечивающим работу RDUI.
Консистентность
Агрегат состоит из главного объекта и подчинённых ему объектов. При изменении данных состояние агрегата пересчитывается так, чтобы сохранить согласованность между ними. Если данных для расчёта недостаточно или новое значение нельзя определить однозначно, зависимые значения очищаются либо остаются незаполненными. Такое состояние является консистентным: в нём нет значений, противоречащих текущим данным и правилам зависимостей. Однако оно может быть невалидным для дальнейшего использования — например, если обязательные поля не заполнены.
Рассмотрим заказ, в котором выбраны организация, контрагент и договор. Если заменить контрагента на того, у которого нет подходящего договора, поле договора очистится. Новое состояние консистентно: заказ больше не ссылается на договор прежнего контрагента. Но оно невалидно, поскольку договор обязателен.
Другой пример — заявка на расход, в которой выбраны статья и соответствующая ей аналитика. Статья, в свою очередь, должна удовлетворять условиям договора. Если изменить договор и прежняя статья перестанет подходить, система подставит новую статью, когда её можно определить однозначно, или очистит поле. Во втором случае зависимая аналитика останется прежней, так как расчет остановится на статье. Агрегат останется в консистентном, но невалидном состоянии до заполнения обязательных данных.
Переиспользование модели расчета для UI и для режима приложений без интерфейса
Одна и та же модель может рассчитывать данные как при работе пользователя с формой, так и при программном заполнении объекта. Для этого зависимости между реквизитами и правила их расчёта описываются отдельно от элементов интерфейса. Форма передаёт модели изменения и отображает результат; сценарий без интерфейса передаёт исходные данные и получает рассчитанный объект.
В проекте KASL-demo-ssl такое переиспользование показано на документе _ДемоЗаявкаНаОперацию. Функция МодельСостояния(Контекст) в модуле менеджера создаёт общую модель документа. Её вызывают и форма документа, и обработчик ОбработкаЗаполнения в модуле объекта. В первом случае контекстом служит форма, во втором — сам объект документа.
Это разделение можно условно сопоставить с MVC: модель отвечает за зависимости и расчёт состояния, представление — за его отображение, контроллер — за передачу изменений и обновление интерфейса. Такое сопоставление описывает роли частей решения, но требует уточнения: сама форма здесь тоже выступает моделируемым объектом со своим состоянием и зависимостями.
Получаются два связанных уровня. Модель документа определяет зависимости данных: например, договор зависит от контрагента, а сумма — от исходной суммы и курса. Модель формы определяет поведение интерфейса для взаимодействия с пользователем: видимость поля зависит от вида операции, возможность редактирования — от состояния документа, заголовок — от номера и даты. Свойства интерфейса рассчитываются тем же механизмом, что и значения реквизитов.
В интерактивном сценарии общая модель документа дополняется моделью формы. В сценарии без интерфейса достаточно модели документа. Поэтому наличие описаний элементов формы в общей схеме само по себе не означает смешения ответственности: механизм поддерживает оба уровня моделирования. Для переиспользования важно направление зависимостей: модель формы может опираться на состояние документа, а расчёт документа должен работать без модели формы.
Инкапсуляция логики расчета
Логика расчёта включает несколько связанных решений: от каких данных зависит значение, при каких условиях оно используется, как вычисляется и какие изменения должны запускать его пересчёт. Когда эти решения распределены по обработчикам событий, для понимания одного реквизита приходится изучать несколько участков кода. Изменение правила требует найти все места, где оно применяется, и согласованно обновить их.
В декларативной модели эти решения объединяются вокруг рассчитываемого параметра. В его описании задаются входные зависимости, условия использования и выражение расчёта. По объявленным связям механизм определяет, какие параметры затронуты изменением и в каком порядке их обрабатывать. Вызывающий код передаёт изменения через общий интерфейс расчёта.
Например, в модели заявки СуммаДокумента зависит от СуммаВзаиморасчетов и КурсРасчетов. Рядом с зависимостями задана формула: сумма взаиморасчётов, умноженная на курс. Это описание связывает правило вычисления с причинами его пересчёта. Если потребуется добавить округление, изменение вносится в формулу параметра. Если появится ещё один исходный показатель, он добавляется в зависимости и учитывается в выражении. После передачи изменений модели механизм пересчитает сумму по обновлённому правилу.
Сложный расчёт можно оформить отдельной функцией прикладного модуля и связать её с параметром модели. В таком случае описание модели показывает, когда и с какими зависимостями используется правило, а функция содержит подробности алгоритма. В KASL-demo-ssl общая модель заявки описана в модуле менеджера документа, а её прикладные обработчики размещены в _ДемоЗаявкаНаОперациюМодель. Каждое правило описывается один раз: модель задаёт его зависимости, а выражение или вызываемая функция — способ расчёта. Эти части могут находиться в разных модулях. Форма и программное заполнение используют одну реализацию правила, поэтому изменение алгоритма вносится в одном месте.
Тот же принцип применяется к модели формы. Например, описание видимости элемента связывает это свойство с параметрами состояния, от которых оно зависит. При изменении этих параметров общий механизм обновления формы применяет рассчитанное свойство. Разработчик может прочитать правило поведения элемента в его декларации.
Такое устройство облегчает сопровождение: у правила есть определённое место, а связи показывают его входные данные и влияние на другие параметры. Оно также позволяет проверять расчёт через модель, задавая исходные значения и проверяя результат. Все изменения, влияющие на расчёт, необходимо передавать модели. Правила расчёта данных, необходимые и форме, и программному заполнению, должны входить в общую модель документа. Например, зависимость суммы документа от суммы взаиморасчётов и курса входит в общую модель. В модели формы остаются правила отображения и взаимодействия с пользователем, включая определение типов и представлений аналитик для настройки полей ввода.
Основные архитектурные слои (паттерн MVC)
MVC

Рисунок 2 — Слои архитектуры в терминах MVC
Reactive Model — общая модель расчёта
Модель описывает параметры состояния, зависимости между ними, условия использования и выражения для вычисления значений. В ней могут участвовать реквизиты документа, поля строк табличных частей и вспомогательные параметры, которые не сохраняются в самом документе.
Например, в общей модели заявки заданы следующие правила:
- договор контрагента выбирается с учётом организации и контрагента;
- валюта взаиморасчётов может определяться по договору;
- сумма документа рассчитывается как сумма взаиморасчётов, умноженная на курс;
- использование банковского счёта зависит от формы оплаты, а его допустимый владелец — от организации.
Эти зависимости объявлены в модуле менеджера документа. Прикладные обработчики модели вынесены в общий модуль _ДемоЗаявкаНаОперациюМодель, а общий алгоритм расчёта реализован в РаботаСОбъектомКлиентСервер.
При применении модели определяется порядок параметров по графу зависимостей. Затем функция Рассчитать обрабатывает очередь: получает изменённые параметры, находит зависимые и пересчитывает их в установленном порядке. Поэтому вызывающему коду достаточно сообщить, какие исходные значения изменились. Последовательность пересчёта зависимых значений определяется моделью.
Для работы с разными контекстами предусмотрен общий способ доступа к данным. В форме описание модели и вспомогательные значения размещаются в её реквизитах, у объекта документа — в ДополнительныеСвойства. Функция НоваяМодельОбъекта учитывает это различие и предоставляет расчётному механизму одинаковую структуру параметров и связей. Доступ к строкам табличной части также учитывает контекст: в форме используются идентификаторы строк, в объекте — их индексы.
Таким образом, переиспользуются и описание зависимостей, и алгоритм их обработки. Для расчёта объекта создавать форму не требуется.
View — декларация интерфейса
Форма получает общую модель документа и дополняет её описанием интерфейса. В процедуре ПрименитьМодельСостояния задаются связи между параметрами модели и свойствами элементов: заголовками, видимостью, режимом просмотра и другими настройками.
Например, заголовок заявки зависит от номера, даты и состояния документа, режим ТолькоПросмотр — от признака проведения, а видимость колонки суммы взаиморасчётов — от различия валют документа и взаиморасчётов. Эти правила определяют, как рассчитанное состояние будет представлено пользователю.
Обновление интерфейса выполняется отдельным этапом. Функция РаботаСФормойКлиентСервер.ОбновитьФорму получает рассчитанные параметры, находит связанные с ними элементы и применяет их свойства. Здесь же при необходимости обновляются настройки выбора. В сценарии без интерфейса этот этап отсутствует.
Controller — передача изменений и запуск расчёта
Роль контроллера выполняет связующий код формы. Обработчик ПриИзменении определяет параметр модели по изменённому элементу, регистрирует изменение и запускает расчёт. После расчёта форма обновляется по возвращённому списку рассчитанных параметров.
В интерактивном сценарии часть вычислений может выполняться на клиенте. Когда очередь доходит до параметра, который требуется рассчитывать на сервере, механизм возвращает незавершённое состояние расчёта. Код формы передаёт его на сервер, где тот же алгоритм продолжает обработку очереди. Правила расчёта при этом остаются общими; распределение исполнения между клиентом и сервером учитывает доступность операций.
Важно, что реактивность здесь обеспечивается явной передачей изменений в механизм. Произвольное присваивание реквизиту в программном коде само по себе не запускает пересчёт зависимостей. Вызывающий сценарий должен зарегистрировать изменённые параметры либо воспользоваться предусмотренным заполнением с последующим расчётом.
Расчёт без интерфейса
В модуле объекта заявки уже есть пример такого вызова. Обработчик ОбработкаЗаполнения подготавливает шаблон исходных значений: форму оплаты, направление и вид операции, курс и сумму взаиморасчётов. Затем получает общую модель через МодельСостояния(ЭтотОбъект), применяет её к документу и вызывает РаботаСОбъектом.ЗаполнитьИРассчитать.
Внутри ЗаполнитьИРассчитать выполняются два шага. Сначала Заполнить переносит значения шаблона в объект и возвращает изменённые параметры. Затем Рассчитать использует этот список для обработки зависимостей. Обработчики элементов формы и обновление интерфейса для этого не нужны.
Так, правило расчёта суммы документа объявлено один раз. В форме его запускает изменение суммы взаиморасчётов или курса, а при программном заполнении — передача этих значений через шаблон. Обоим сценариям доступна одна формула и один механизм распространения изменений.
Тот же подход можно использовать при загрузке данных, выполнении фоновых заданий или пакетном создании документов: подготовить объект, применить общую модель, передать изменения и запустить расчёт. В исследованном демо подтверждён именно вызов из ОбработкаЗаполнения; перечисленные интеграционные сценарии — возможные способы применения механизма. Сам обработчик заполнения может вызываться и при интерактивном создании документа, но его расчёт не требует контекста формы.
Границы переиспользования
Переиспользование модели предполагает общие правила расчёта данных, при этом последовательность действий пользователя и программного заполнения может различаться. Зависимости, необходимые обоим сценариям, должны входить в общую модель, а её выражения и обработчики — работать в обоих контекстах. Форма дополняет эту модель сценариями взаимодействия с пользователем.
Например, в модели задана зависимость «Договор → Валюта взаиморасчётов»: выбранный договор определяет валюту. Однако пользователю может быть удобнее сначала выбрать нужную валюту, а затем подобрать договор, который соответствует ей и остальным условиям, включая организацию и контрагента. В демо для этого предусмотрен обработчик ручного изменения валюты в форме.
Такой сценарий меняет порядок пользовательского ввода. Для его работы необходимо «ослабить» связь «Договор → Валюта взаиморасчётов»: по умолчанию она также ограничивает выбор в интерфейсе и не позволяет пользователю выбрать валюту, не соответствующую текущему договору. Если установить для связи признак Слабая = Истина, она перестанет препятствовать пользовательскому выбору, сохранив строгое действие при расчёте модели. Выбранная пользователем валюта выступает условием поиска договора; после его выбора расчёт следует обычным зависимостям модели. Таким образом, ослабляется ограничение интерактивного выбора, а сама расчётная зависимость сохраняется.
Для реализации обратного подбора необходимо написать обработчик события ПриИзменении параметра ВалютаВзаиморасчетов. Он подбирает договор по выбранной валюте с учётом остальных зависимостей и передаёт изменение договора в модель для последующего пересчёта. Таким образом, обратное направление «Валюта → Договор» реализуется явно в обработчике изменения параметра модели; постоянная обратная зависимость в граф не добавляется. Признак Слабая разрешает выбор валюты в интерфейсе, а алгоритм подбора задаёт разработчик. Такой обработчик можно описать как в дополнении модели для формы, так и в общей модели документа. Если подбор нужен и при программном заполнении, обработчик включается в общую модель и работает при передаче ей изменений параметра ВалютаВзаиморасчетов, в том числе без интерфейса. Его размещение определяется тем, каким сценариям требуется этот алгоритм.
Другой пример расширения модели для интерфейса — определение типов и представлений аналитик строки. В демо эти вспомогательные параметры рассчитываются в модуле формы, чтобы настроить поля ввода и показать пользователю понятные названия аналитик. Они описывают, как пользователю работать с данными. Для расчёта документа без интерфейса эти параметры избыточны, поэтому их размещение в модели формы соответствует разделению ответственности. Общая модель определяет зависимости данных документа, а модель формы дополняет их зависимостями, необходимыми для удобного ввода и отображения.
Завершение расчёта не заменяет проверку готовности документа к использованию. Как и в интерактивном режиме, результат может оставаться невалидным из-за незаполненных обязательных данных. Проверка перед записью или дальнейшей обработкой остаётся отдельным шагом вызывающего сценария.
Работа с таблицами на форме
Работа с таблицами

Рисунок 3 — Связанные состояния таблицы на форме
У таблицы на форме есть несколько связанных состояний: значения строк, выбранная строка, режим редактирования и условия отображения. Модель позволяет описывать зависимости между ними и другими элементами формы. Например, выбор строки может менять содержимое панели подробностей и отбор в соседней таблице, а завершение редактирования — запускать пересчёт параметров, зависящих от таблицы.
При подключении модели механизм назначает общие обработчики событий таблицы: активизации строки, добавления, изменения, окончания редактирования, удаления и выбора значения. Автоматическое назначение выполняется, если обработчик события ещё не задан. Если форма использует собственный обработчик, вызов соответствующего механизма нужно включить в него. Поведение общих обработчиков определяется описанием таблицы и её параметров в модели.
Параметр Таблицы.<ИмяТаблицы>.ТекущаяСтрока
Выбранную строку можно включить в граф зависимостей как отдельный параметр состояния. Для этого в описании элемента таблицы вызывается метод ТекущаяСтрока(). По умолчанию он создаёт параметр с именем Таблицы.<ИмяТаблицы>.ТекущаяСтрока, где <ИмяТаблицы> — имя элемента формы. При необходимости имя параметра можно передать явно. Если параметр с таким именем уже существует, используется его описание.
Свойство ПараметрТекущаяСтрока в модели элемента формы хранит ссылку на этот параметр. Значением самого параметра служит идентификатор текущей строки. При активизации другой строки механизм получает новое значение из таблицы формы, записывает его в модель и регистрирует изменение. Зависимые параметры рассчитываются через механизм отложенного вызова; во время редактирования строки обработчик активизации не запускает этот расчёт.
Например, для таблицы ПараметрыСостояния имя по умолчанию будет Таблицы.ПараметрыСостояния.ТекущаяСтрока. От него можно сделать зависимым параметр с данными выбранной строки, а затем связать с этими данными поле выражения, заголовок панели или отбор списка связей. Получится цепочка: выбор строки → получение её данных → обновление зависимых элементов интерфейса.
При описании таких зависимостей нужно использовать то же имя, которое задано при вызове ТекущаяСтрока(), и учитывать отсутствие выбранной строки. В этом случае значение может быть Неопределено; выражение должно определить подходящее пустое состояние панели или отбора. Идентификатор строки используется для поиска её данных и не является порядковым номером строки на экране.
Открытие элементов в полях таблицы в режиме ТолькоПросмотр
Даже когда редактирование таблицы запрещено, пользователю может понадобиться открыть объект, указанный в её ячейке: например, договор, статью или контрагента. Механизм сохраняет возможность такого перехода через обработчик выбора значения.
Обработчик ВыборСтрокиТаблицыФормы проверяет признак ТолькоПросмотр у выбранного поля, его непосредственного родителя, таблицы и формы. Если хотя бы на одном из этих уровней включён режим просмотра, механизм находит параметр модели, связанный с полем, получает его значение для выбранной строки и передаёт его в ПоказатьЗначение. Для этого поле должно быть представлено в модели и связано с параметром данных.
Так пользователь может перейти к подробностям из формы, закрытой для редактирования. Признак ТолькоПросмотр в данном сценарии относится к исходному полю или форме: обработчик не передаёт отдельный запрет редактирования в открываемую карточку. Её доступность определяется собственными настройками и правами пользователя.
Транзакционное редактирование строки таблицы
Редактирование строки рассматривается как действие, которое пользователь может завершить или отменить. Перед изменением существующей строки механизм сохраняет значения её колонок в состоянии формы. При отмене эти значения восстанавливаются из снимка. Такая транзакционность действует на уровне данных строки в форме; транзакция базы данных для этого не открывается.
Здесь различаются изменение отдельного поля и уведомление модели об изменении таблицы. Платформенное событие ПриИзменении срабатывает при изменении каждого поля, поэтому расчёты, зависящие от этого поля, могут выполняться ещё во время ввода. Контракт транзакционного редактирования предусматривает уведомление об изменении таблицы после завершения операции над строкой — как после подтверждения, так и после отмены редактирования. Подсистема модели состояния формирует это уведомление через отложенные изменения. Зависимости от таблицы рассчитываются по принятому либо восстановленному состоянию строки.
Перед завершением редактирования могут выполняться проверки, заданные моделью. Если у параметра таблицы заполнено свойство Ключ, механизм проверяет заполнение используемых обязательных полей и отсутствие другой строки с теми же значениями ключевых колонок. Обязательность и использование поля определяются свойствами соответствующего параметра модели, поэтому могут зависеть от текущих данных. При нарушении условий пользователь получает сообщение и должен исправить значения или отменить ввод.
В текущей реализации обе проверки в этом обработчике выполняются при заданном Ключ. Проверка заполнения охватывает все используемые обязательные колонки строки, а проверка повторов — колонки, перечисленные в ключе. Например, в демо-заявке для таблицы ДвиженияОперации задан ключ ОбъектИнвестирования, СтатьяДДС: сочетание этих значений должно быть уникальным. Для обычной таблицы совпадения ищутся по всей коллекции; для дерева — среди строк одного родителя.
При подтверждении редактирования сохраняются введённые значения строки, а при отмене изменения существующей строки восстанавливается её снимок. Если пользователь отменяет добавление, добавленная строка удаляется. Для дерева подсистема выполняет это удаление явно, поскольку механизм добавления строк в дереве отличается от механизма обычной таблицы. Подробнее эти особенности разобраны в отдельной публикации. Уведомление об изменении таблицы относится к завершению всей операции: отложенный пересчёт позволяет обновить связанные данные и интерфейс после принятия или восстановления состояния строки.
При этом изменение отдельного поля может запускать расчёт ещё во время редактирования строки. При отмене значения колонок восстанавливаются из снимка, но восстановленные поля повторно не регистрируются как изменённые параметры модели. Поэтому для параметров, зависящих от полей строки, необходимо дополнительно объявлять зависимость от самой таблицы. Тогда при регистрации изменения таблицы эти параметры смогут пересчитаться по её актуальному состоянию, включая восстановленные значения строки.
На стороне прикладной модели достаточно объявить зависимость от самой таблицы. По этому контракту подсистема самостоятельно регистрирует изменение таблицы после подтверждения или отмены редактирования строки и запускает пересчёт через механизм отложенных изменений. Дополнительный обработчик отмены или ручной запуск расчёта в прикладном коде для этого не требуется.
Это поведение отличается от стандартного поведения платформы 1С: изменение каждого поля строки вызывает обработчики
ПриИзменениикак самого поля, так и таблицы. В описываемой модели уведомление об изменении таблицы формируется после подтверждения или отмены редактирования строки.
Автозаполнение новой строки по параметру ОтборСтрок (в проекте)
Планируется использовать текущий отбор таблицы как источник начальных значений новой строки. Это полезно для связанных таблиц: пользователь выбирает запись в основной таблице, а подчинённая показывает только относящиеся к ней строки. При добавлении строки значения, определяющие эту принадлежность, можно сразу взять из отбора.
Например, если таблица связей отобрана по идентификатору выбранного параметра, новая связь должна сразу получить этот идентификатор. Пользователю не придётся повторно выбирать владельца, а добавленная строка будет соответствовать условиям отображения текущей таблицы.
Предполагаемая последовательность работы: получить актуальное значение ОтборСтрок, перенести значения полей отбора в новую строку и передать изменения модели для расчёта зависимых параметров. Такой способ подходит для отбора, который явно задаёт равенство полей конкретным значениям. Эти значения служат исходными данными; дальнейшее заполнение и проверка строки выполняются по правилам модели.
Сейчас в механизме есть применение свойства ОтборСтрок к таблице формы, а автоматический перенос его значений в добавляемую строку относится к планируемому развитию. Наличие отбора само по себе пока не означает, что поля новой строки будут заполнены по нему.
Внедрение подсистемы в форму
Внедрение в форму

Рисунок 4 — Подключение формы к подсистеме
Подключение формы состоит из двух частей: в её модуль добавляется служебный код взаимодействия с подсистемой, а в модели описываются параметры, зависимости и поведение элементов интерфейса. Ниже рассматривается подключение к конфигурации, в которой подсистема уже присутствует. В качестве образца можно использовать форму документа _ДемоЗаявкаНаОперацию из KASL-demo-ssl.
Подготовить модуль формы
В модуль формы переносится область ОбработчикиСобытийФормыВМоделиСостояния из демонстрационного примера. Она содержит общую обработку изменений и начала выбора, процедуры расчёта и обновления формы на клиенте и сервере, а также обработчики событий таблиц. Вместе с областью нужно перенести клиентские экспортные переменные ОтложенныеПараметры и СостояниеТаблицыФормы.
Эти процедуры связывают события формы с общими модулями подсистемы. Например, ПриИзменении определяет параметр модели по элементу формы, регистрирует изменение и передаёт его в ПриИзмененииНаКлиенте. Если расчёт требует обращения к серверу, процедура РассчитатьНаСервере продолжает его из полученного состояния. После расчёта вызываются процедуры обновления формы.
Подсистема назначает событиям имена обработчиков, но не создаёт их тела в модуле формы. В частности, экспортная процедура РассчитатьОтложенныеПараметры должна быть доступна для отложенного вызова.
Автоматическое подключение обработчиков означает назначение уже существующих процедур событиям формы и её элементов. Служебный шаблон модуля формы разработчик добавляет при внедрении.
Создать модель и описать зависимости
Описание удобно разместить в отдельной серверной процедуре формы, например ПрименитьМодельСостояния. Если для объекта уже есть общая модель, форма получает её и дополняет правилами интерфейса. В демо-заявке для этого используется функция МодельСостояния модуля менеджера документа, которой передаётся контекст формы.
Для самостоятельной формы модель создаётся через РаботаСМодельюСостояния.НоваяМодельСостояния. При создании указываются контекст, при необходимости путь к данным объекта и модуль с прикладными обработчиками модели. Если у формы есть реквизит Объект, его использование определяется автоматически. Для другого расположения данных путь задаётся явно.
Далее описываются параметры данных, их зависимости и выражения расчёта. Для элементов формы задаются свойства и параметры, от которых эти свойства зависят: например, видимость поля, заголовок страницы или ограничение типа аналитики. Параметры, которые можно рассчитывать на клиенте, отмечаются соответствующим признаком; правила, требующие серверных данных, выполняются на сервере.
Для таблиц при необходимости подключается ТекущаяСтрока, задаётся ключ строки и описываются зависимости от таблицы в целом. Общие правила документа остаются в общей модели, а параметры удобного ввода и отображения добавляются в модели формы.
Применить модель и выполнить начальное обновление
После завершения описания вызывается метод ПрименитьМодель. Он включает элементы формы в схему, назначает свободные обработчики событий, выполняет заданную инициализацию данных, определяет порядок расчёта параметров и рассчитывает вспомогательные параметры контекста. Затем описание модели переносится в форму и настраиваются параметры выбора полей.
Служебные реквизиты ПараметрыСостояния, Связи, ЭлементыФормы, ПараметрыЭлементов, ЗначенияПараметров, ПутьКДаннымОбъекта и МодульМодели подсистема добавляет программно. Создавать их вручную в редакторе формы не требуется. При этом прикладные реквизиты и элементы интерфейса, на которые ссылается модель, должны существовать к моменту её применения.
В обработчике ПриСозданииНаСервере вызывается процедура построения и применения модели, а затем ОбновитьФормуНаСервере. Такое начальное обновление применяет рассчитанные свойства к интерфейсу до начала работы пользователя. Применение схемы рассчитывает параметры контекста; заполнение и пересчёт данных самого объекта выполняются предусмотренным для него сценарием.
При открытии формы также должна выполниться процедура ПриОткрытииВМоделиФормы: через общий модуль она инициализирует очередь отложенных параметров и состояние редактирования таблицы. Эта инициализация необходима для дальнейшей обработки событий строк.
Согласовать существующие обработчики
Подсистема назначает обработчик, только если событие ещё не занято. Если у поля уже есть собственный ПриИзменении, применение модели сохранит его. Разработчику нужно включить в эту процедуру передачу изменений в модель, согласовав её с прикладным алгоритмом.
Ниже перечислены вызовы, которые нужно добавить в существующие обработчики. Они обращаются к служебным процедурам, перенесённым в модуль формы из демонстрационного шаблона.
События формы и полей
| Событие | Вызов из существующего обработчика |
|---|---|
Форма: ПриОткрытии |
ПриОткрытииВМоделиФормы(Отказ); |
Форма: ПриЧтенииНаСервере |
ПриЧтенииВМоделиФормыНаСервере(ТекущийОбъект); |
Форма: ПослеЗаписиНаСервере |
ПослеЗаписиВМоделиФормыНаСервере(ТекущийОбъект, ПараметрыЗаписи); |
Поле, в том числе поле колонки таблицы: ПриИзменении |
ПриИзменении(Элемент); |
Поле: НачалоВыбора |
НачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка); |
Например, если у поля уже назначен обработчик ВалютаВзаиморасчетовПриИзменении, передача изменения в модель выглядит так:
Листинг 1 — Передача изменения поля в модель из существующего обработчика
&НаКлиенте
Процедура ВалютаВзаиморасчетовПриИзменении(Элемент)
// Здесь выполняется существующий прикладной алгоритм.
ПриИзменении(Элемент);
КонецПроцедуры
Вызов ПриИзменении(Элемент) регистрирует изменение параметра, связанного с переданным полем. Если прикладной алгоритм меняет несколько значений — например, подбирает договор по выбранной валюте, — модели нужно сообщить обо всех изменениях, влияющих на зависимости. Порядок прикладной обработки и вызова подсистемы определяется сценарием; при объединении следует исключить повторное выполнение одного и того же расчёта через старые обработчики и новый механизм.
Для форм объектов со ссылкой подсистема подключает обновление после чтения и записи. В используемом модуле модели должна быть предусмотрена экспортная процедура ИнициализироватьДанные, принимающая модель объекта: текущий общий обработчик чтения вызывает её перед пересчётом параметров контекста и обновлением формы.
События таблицы формы
В примерах ниже параметр существующего обработчика, содержащий таблицу формы, назван Элемент. Он передаётся первым аргументом служебной процедуры — в шаблоне этот аргумент называется ТаблицаФормы.
| Событие таблицы | Вызов из существующего обработчика |
|---|---|
Выбор |
ВыборСтрокиТаблицыФормы(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка); |
ПриАктивизацииСтроки |
ПриАктивизацииСтрокиТаблицыФормы(Элемент); |
ПередНачаломДобавления |
ПередНачаломДобавленияСтрокиТаблицыФормы(Элемент, Отказ, Копирование, Родитель, Группа, Параметр); |
ПередНачаломИзменения |
ПередНачаломИзмененияСтрокиТаблицыФормы(Элемент, Отказ); |
ПриНачалеРедактирования |
ПриНачалеРедактированияСтрокиТаблицыФормы(Элемент, НоваяСтрока, Копирование); |
ПередОкончаниемРедактирования |
ПередОкончаниемРедактированияСтрокиТаблицыФормы(Элемент, НоваяСтрока, ОтменаРедактирования, Отказ); |
ПриОкончанииРедактирования |
ПриОкончанииРедактированияСтрокиТаблицыФормы(Элемент, НоваяСтрока, ОтменаРедактирования); |
ПередУдалением |
ПередУдалениемСтрокиТаблицыФормы(Элемент, Отказ); |
Для таблиц важно сохранить весь служебный путь: сохранение состояния строки, проверки перед завершением, обработку отмены и отложенные изменения. Процедура ПриНачалеРедактированияСтрокиТаблицыФормы в демонстрационном шаблоне также обновляет настройки выбора колонок для текущей строки, поэтому здесь вызывается именно процедура модуля формы.
Если существующий обработчик уже называется так же, как служебная процедура шаблона, их тела объединяются в одной процедуре. Добавлять вызов процедуры из неё самой не нужно.
Подключить программные изменения и проверить сценарии
Изменения, выполненные командами формы или серверным кодом, также передаются через механизм модели. В демо для этого используется РаботаСОбъектомКлиентСервер.Заполнить: функция применяет шаблон значений и возвращает изменённые параметры, которые затем передаются в ПриИзмененииНаСервере. Такой путь запускает расчёт зависимостей и обновление интерфейса. При прямом присваивании реквизитов регистрацию изменений нужно выполнить явно.
После подключения следует проверить открытие нового и существующего объекта, изменение исходных реквизитов, переход расчёта с клиента на сервер, параметры выбора и обновление формы после записи. Для таблиц дополнительно проверяются смена текущей строки, добавление, подтверждение и отмена редактирования, удаление и обновление зависимых элементов. Эти сценарии показывают, что описание модели, служебные обработчики и существующая логика формы работают согласованно.
Описание модели объекта
Модель объекта

Рисунок 5 — Состав модели объекта
Модель описывается программно: разработчик объявляет параметры состояния, связывает их с исходными данными и задаёт правила расчёта. Для этого в подсистеме предусмотрен DSL — набор методов, вызовы которых образуют читаемое описание модели. В KASL-demo-ssl общая модель документа размещена в модуле менеджера, дополнения для интерфейса — в модуле формы, а прикладные функции расчёта и обработки изменений — в отдельном общем модуле.
Модель объекта
Общая модель заявки создаётся функцией МодельСостояния(Контекст) модуля менеджера документа. Внутри неё вызывается:
Листинг 2 — Создание общей модели документа
МодельСостояния = РаботаСМодельюСостояния.НоваяМодельСостояния(
Контекст, , "_ДемоЗаявкаНаОперациюМодель");
Контекст определяет, с какими данными будет работать модель: в интерактивном сценарии это форма, при программном заполнении — объект документа. Второй аргумент задаёт путь к основному объекту внутри контекста; по-умолчанию этот путь для формы "Объект". Третий аргумент указывает модуль прикладных обработчиков; разделение клиент-серверного контекста определяется директивами компиляции.
Функция менеджера МодельСостояния(ЭтотОбъект) возвращает схему модели объекта. Вызывающий код может дополнить его и затем вызвать ПрименитьМодель(). Поэтому форма получает общие правила документа до добавления собственных зависимостей интерфейса.
Основная единица описания — параметр состояния. Он может представлять реквизит объекта, таблицу, поле строки или вспомогательное значение, которое существует только в модели. Например:
| Объявление | Что описывает |
|---|---|
ПараметрСостояния("СуммаДокумента") |
Реквизит документа |
ПараметрСостояния("ДвиженияОперации") |
Табличную часть целиком |
ПараметрСостояния("ДвиженияОперации.Сумма") |
Поле строки табличной части |
ПараметрСостояния("ЭтоВнешняяОперация").ТипБулево() |
Вспомогательный логический признак |
Путь к данным задаётся относительно объекта: для реквизита документа достаточно СуммаДокумента, без префикса Объект. Префикс пути к основному объекту учитыватся автоматически. Для того, чтобы различать параметры состояния основного объекта или контекста есть признак Контекстный: если параметр относится к внешнему контексту - его значение Истина, иначе, если к объекту - Ложь. По умолчанию этот путь служит и именем параметра. Повторный вызов ПараметрСостояния с тем же именем позволяет дополнить уже существующее описание.
Связи и формула задаются рядом с параметром, который должен рассчитываться. Например, фрагмент общей модели заявки:
Листинг 3 — Параметр состояния с зависимостями и выражением
МодельСостояния.ПараметрСостояния("СуммаДокумента").НаКлиенте()
.Параметр("СуммаВзаиморасчетов")
.Параметр("КурсРасчетов")
.Выражение("Параметры.СуммаВзаиморасчетов * Параметры.КурсРасчетов")
;
ПараметрСостояния задает объект расчета, Параметр объявляет его входящие зависимости, а Выражение задаёт формулу. При расчёте входные значения доступны через структуру Параметры, текущее значение рассчитываемого параметра — через переменную Значение. Изменение суммы взаиморасчётов или курса является триггером для пересчёта суммы документа.
Порядок вызовов в описании не задаёт последовательность пересчёта: она определяется по графу зависимостей. DSL такого вида декларативно настраивают текущий параметр состояния, его связи или свойства и является позиционным. Например, Использование сразу после ПараметрСостояния относится к самому параметру, а после Параметр — к выбранной связи.
Входное значение параметра можно переопределить. В описании строки заявки используется .Параметр("Сумма", "ДвиженияОперации.Сумма"): источником служит поле строки, а в выражении его значение доступно как Параметры.Сумма. Аналогично формируются вложенные параметры отбора:
Листинг 4 — Связи отбора ссылочного параметра
МодельСостояния.ПараметрСостояния("ДоговорКонтрагента")
.НеИспользование("ЭтоРасчетыБезДоговора")
.Параметр("Отбор.Организация", "Организация")
.Параметр("Отбор.Владелец", "Контрагент")
;
Здесь договор используется, когда признак ЭтоРасчетыБезДоговора ложен. Организация и контрагент передаются как поля структуры Параметры.Отбор. Для ссылочного параметра без собственного выражения подсистема использует стандартный подбор по этим условиям: проверяет соответствие текущего значения отбору и, если разрешено заполнение по умолчанию, заполняет пустое значение при единственном подходящем варианте. На форме те же связи участвуют в настройке выбора.
Условия можно задавать и для отдельных связей. Например, владелец счёта контрагента в зависимости от вида операции определяется организацией или контрагентом. В модели это несколько связей с одним путём Отбор.Владелец, каждая со своим условием Использование:
Листинг 5 — Условия использования для отдельных связей
МодельСостояния.ПараметрСостояния("СчетКонтрагента")
.Параметр("Отбор.Владелец", "Организация")
.Использование("ЭтоВнутренняяОперация")
.Параметр("Отбор.Владелец", "Контрагент")
.Использование("ЭтоВнешняяОперация")
;
При внутренней операции в Отбор.Владелец передаётся организация, при внешней — контрагент. В этом примере признаки операций взаимоисключающие, поэтому используется одна из двух связей. Каждый вызов Использование относится к предшествующей связи Параметр и определяет, когда её значение входит в условия подбора счёта.
Некоторые настройки уточняют поведение расчёта:
ТипБулево()задаёт тип вспомогательного признака и отключает проверку его заполненности:Ложьявляется допустимым значением.ПроверкаЗаполнения(Ложь)у параметра состояния или связи разрешает использовать пустое значение. Это нужно, когда формула должна обработать отсутствие данных, например определить признак нового объекта по пустой ссылке.НаКлиенте()разрешает клиентский расчёт,НаСервере()задаёт серверное исполнение. Обращение к базе или серверному прикладному модулю требует серверного расчёта; простой арифметический пример выше может выполняться на клиенте. В сценарии без интерфейса он также рассчитывается на сервере.Слабая()ослабляет ограничение выбора в интерфейсе, сохраняя зависимость при расчёте модели. Так в демо описана связь договора с валютой взаиморасчётов, позволяющая поддержать пользовательский сценарий выбора валюты перед подбором договора.
Модель формы
Форма получает общую модель вызовом Документы._ДемоЗаявкаНаОперацию.МодельСостояния(ЭтотОбъект) и дополняет её в процедуре ПрименитьМодельСостояния. Дополнения описывают состояние интерфейса и данные, необходимые для работы пользователя.
Для выбора элемента используется ЭлементФормы, для его рассчитываемого свойства — Свойство. Далее задаются зависимости и выражение по тем же правилам, что и для данных объекта. Например, видимость поля суммы взаиморасчётов определяется тем, различаются ли валюты взаиморасчетов и документа:
Листинг 6 — Свойство элемента формы с зависимостями и выражением
МодельСостояния.ЭлементФормы("СуммаВзаиморасчетов")
.Свойство("Видимость")
.Параметр("ВалютаВзаиморасчетов")
.Параметр("ВалютаДокумента")
.Выражение("Параметры.ВалютаВзаиморасчетов <> Параметры.ВалютаДокумента")
;
ЭлементФормы — имя элемента интерфейса. Оно может отличаться от имени реквизита, с которым связан элемент. Вызов ЭлементФормы() без аргумента выбирает саму форму (ЭтаФорма): в демо таким способом описаны её заголовок и свойство ТолькоПросмотр:
Листинг 7 — Свойства самой формы
МодельСостояния.ЭлементФормы()
.Свойство("Заголовок")
.Параметр("Номер")
.Параметр("Дата")
.Выражение("СтрШаблон(НСтр('ru = ''Заявка на операцию %1 от %2'''), Параметры.Номер, Параметры.Дата)")
.Свойство("ТолькоПросмотр")
.Параметр("Проведен")
.Выражение("Параметры.Проведен")
;
Заголовок зависит от номера и даты документа и обновляется при их изменении. Свойство ТолькоПросмотр определяется признаком проведения: проведённый документ отображается в режиме просмотра. В полном примере демо заголовок дополнительно учитывает проведение, пометку удаления и признак нового объекта.
Если нужное значение уже рассчитывается параметром модели, свойство можно связать с ним напрямую:
Листинг 8 — Связывание свойства с готовым параметром модели
МодельСостояния.ЭлементФормы("ДвиженияОперации").НаКлиенте()
.Свойство("Видимость", "ЭтоВнешняяОперация")
;
Здесь дополнительная формула не нужна: видимость таблицы определяется готовым логическим признаком. Изменение его значения учитывается при обновлении формы.
Модель формы также описывает вспомогательные данные для полей ввода. В заявке это виды, типы значений и наименования аналитик строки. Они позволяют настроить ввод и отображение аналитики по выбранной статье ДДС. Такие данные нужны интерфейсу и не обязательны для расчёта документа без формы.
После добавления всех правил вызывается МодельСостояния.ПрименитьМодель(). Подключение служебных обработчиков и начальное обновление интерфейса выполняются в порядке, описанном в разделе «Внедрение подсистемы в форму».
Работа с вычисляемым параметром состояния
Признак Вычисляемый определяет способ получения значения параметра. При Вычисляемый = Истина результат не сохраняется как отдельное значение модели: выражение выполняется при обращении к параметру. Это удобно для вспомогательных условий, свойств интерфейса и значений, которые нельзя сериализовать для хранения в контексте формы.
Наличие формулы само по себе не делает параметр вычисляемым в этом смысле. Например, сумма документа рассчитывается по выражению, но результат сохраняется в реквизите. Вычисляемый параметр предоставляет значение по запросу, используя актуальные входные данные.
| Поведение | Обычный параметр с выражением | Вычисляемый параметр |
|---|---|---|
| Хранение результата | В реквизите объекта, контекста или в ЗначенияПараметров |
Отдельное сохранённое значение не используется |
| Получение значения | Читается сохранённый результат | Выполняется выражение |
| Начальный расчёт параметров контекста | Параметр участвует, если относится к контексту | Параметр пропускается |
| Обработка зависимого параметра в очереди расчёта | Изменение определяется по результату расчёта | Параметр считается изменённым без сравнения значений |
Вычисление выполняет функция РаботаСОбъектомКлиентСервер.ПолучитьЗначение: она собирает входящие параметры, выполняет выражение и возвращает результат. Если необходимые входные данные недоступны и сбор параметров завершился отказом, возвращается пустое значение заданного типа либо Неопределено, если тип не задан.
Повторное обращение может снова выполнить выражение. Поэтому такой расчёт следует описывать без побочных действий: получение значения не должно само по себе изменять другие данные. Выражение опирается на входные параметры; сохранённого предыдущего результата, на который можно было бы рассчитывать через переменную Значение, у него нет.
Свойства формы. В текущей реализации DSL метод Свойство создаёт вычисляемые параметры для свойств элементов формы, кроме Заголовок. Например:
Листинг 9 — Вычисляемый параметр свойства формы
МодельСостояния.ЭлементФормы()
.Свойство("ТолькоПросмотр")
.Параметр("Проведен")
.Выражение("Параметры.Проведен")
;
Созданный параметр ЭтаФормаТолькоПросмотр получает Вычисляемый = Истина. При обновлении формы механизм запрашивает его значение и применяет результат к свойству формы. Аналогично описываются Видимость, ПоложениеЗаголовка и другие свойства. Само свойство элемента получает рассчитанное значение, но отдельный результат в хранилище параметров модели для него не нужен.
Исключение для Заголовок задано непосредственно в DSL: его параметр создаётся с Вычисляемый = Ложь. Если свойство связывается с уже существующим параметром, используется его прежний признак — повторное получение параметра не меняет Вычисляемый.
Условия для строк таблицы. Другой случай автоматического применения признака — вспомогательное условие параметра строки, у которого нет собственного пути к данным. Например, фрагмент модели аналитики:
Листинг 10 — Вычисляемое условие использования для строки таблицы
МодельСостояния.ПараметрСостояния("ДвиженияОперации.Аналитика1")
.Использование()
.Параметр("ДвиженияОперации.ВидАналитики1")
.Выражение("ЗначениеЗаполнено(Параметры.ДвиженияОперации.ВидАналитики1)")
;
Метод Использование() создаёт вспомогательный логический параметр. Поскольку он относится к строке таблицы и не связан с отдельным реквизитом, подсистема помечает его как вычисляемый. Условие определяется для той строки, с которой работает расчёт: аналитика используется, если в этой строке заполнен её вид. Для хранения результата условия не требуется дополнительная колонка.
На уровне общего модуля признак можно задать последним аргументом функции РаботаСМодельюСостояния.ПараметрСостояния(Схема, Путь, Имя, Контекстный, Тип, Вычисляемый) при создании параметра. В рассмотренных примерах DSL устанавливает его автоматически.
Вычисление при обращении сохраняет необходимость объявлять зависимости. Связи показывают, какие параметры и элементы формы затронуты изменением исходных данных. Когда вычисляемый параметр попадает в очередь как зависимый, механизм считает его изменённым и продолжает обработку зависимостей, даже если при следующем вычислении результат окажется прежним.
Обработчики модели
Короткий расчёт удобно задать строкой выражения. Для более сложного алгоритма модель может вызвать функцию прикладного модуля. В DSL предусмотрены следующие варианты:
| Объявление | Вызываемый обработчик по умолчанию |
|---|---|
Выражение() или Выражение("*") |
ЗначениеПараметра<ИмяПараметра>(Параметры, Значение, СтандартнаяОбработка, Отказ) |
ПриИзменении() |
ПриИзмененииПараметра<ИмяПараметра>(МодельОбъекта, РасчетныйПараметр) |
ФункцияСостояния() для элемента формы |
СвойстваЭлемента<ИмяЭлемента>(Свойства, Параметры) |
Имена в угловых скобках заменяются именами описываемых параметров или элементов. При формировании имени обработчика параметра точки удаляются: для ДвиженияОперации.СтатьяДДС получается ПриИзмененииПараметраДвиженияОперацииСтатьяДДС. Обработчики размещаются в модуле, указанном при создании модели, и доступны для внешнего вызова. Вместо соглашения об именовании можно передать в соответствующий метод явную строку вызова.
Функция расчёта значения получает собранные входные параметры и текущее значение, затем возвращает результат. Например, в демо ОрганизацияКонтрагента описана через Выражение("*"), а функция ЗначениеПараметраОрганизацияКонтрагента определяет организацию по контрагенту. Зависимость от контрагента остаётся в декларации модели, подробности получения организации — в функции. Параметр Отказ позволяет отказаться от применения рассчитанного значения.
Обработчик изменения подключается отдельно от формулы и выполняется механизмом расчёта при обработке изменения параметра. Например:
Листинг 11 — Подключение обработчика изменения параметра
МодельСостояния.ПараметрСостояния("ДвиженияОперации.СтатьяДДС").НаСервере()
.ПриИзменении()
;
В форме заявки это объявление дополняет параметр из общей модели. Функция ПриИзмененииПараметраДвиженияОперацииСтатьяДДС получает выбранную статью, определяет вид аналитики, её тип и наименование и заполняет вспомогательные поля той же строки. РасчетныйПараметр содержит сведения о параметре и идентификатор строки, поэтому обработчик работает с нужной строкой таблицы.
Дополнительные изменения внутри такого обработчика следует выполнять с регистрацией в модели. В демо используется РаботаСОбъектомКлиентСервер.ЗаполнитьОбъект: заполнение включается в обработку изменений, и зависимые параметры могут быть пересчитаны.
ПриИзменении()в описании модели и событиеПриИзмененииполя формы относятся к разным уровням. Событие поля передаёт пользовательское изменение в модель. Обработчик модели выполняет прикладное действие в ходе расчёта и может использоваться также без интерфейса, если подключён в общей модели и работает с доступными в этом контексте данными.
Функция состояния элемента позволяет описать несколько свойств одним алгоритмом. Она получает структуру входных значений Параметры и заполняет структуру Свойства, которую подсистема затем применяет к элементу. Зависимости от данных объявляются у элемента через Параметр. Такой вариант полезен, когда несколько настроек элемента определяются общей логикой; для независимых свойств обычно достаточно отдельных объявлений Свойство.
Обработчик инициализации
Инициализация подготавливает данные, которые нужны модели до начала обычной обработки изменений. Она подключается отдельным вызовом:
Листинг 12 — Подключение обработчика инициализации
МодельСостояния.ОбработчикИнициализацииДанных();
При применении модели подсистема вызывает экспортную процедуру ИнициализироватьДанные из заданного модуля обработчиков. Ей передаётся схема с доступом к контексту и объекту. Вызов выполняется до определения порядка расчёта и начального расчёта параметров контекста.
В демо инициализация подключена при построении модели формы. Процедура ИнициализироватьДанные(МодельОбъекта) проверяет, что работает с формой, и подготавливает для уже существующих строк заявки вид аналитики, тип её значения и наименование. Данные получаются сразу для набора строк. После этого интерфейс может корректно настроить поля аналитик ещё до первого изменения статьи пользователем.
Та же процедура вызывается служебной обработкой чтения и записи объекта в форме. Поэтому она должна допускать повторное выполнение: восстановить вспомогательные данные по текущему состоянию объекта, не добавляя повторно строки и не меняя прикладные данные только из-за повторного вызова.
Начальное заполнение документа задаётся своим сценарием. В ОбработкаЗаполнения демо-документа сначала создаётся и применяется общая модель, затем шаблон исходных значений передаётся в РаботаСОбъектом.ЗаполнитьИРассчитать. Зависимости рассчитываются по переданным изменениям. Обработчик инициализации используется для подготовки контекста, а правила дальнейшего пересчёта остаются в параметрах, связях и обработчиках модели.
Практикум
Начнём с расчёта обычного объекта, затем перенесём те же принципы на форму: настроим выбор договора, видимость полей, заголовки и связь между таблицами. В каждом примере сначала описываются зависимости, затем модели передаются исходные данные или пользовательские изменения.
Работа с объектом
Следующие примеры кода можно запускать в любой удобной для вас консоли кода. Я предпочитаю использовать Инструменты разработчика.
Консоль кода

Рисунок 6 — Консоль выполнения примеров практикума
В первых трёх примерах контекстом служит структура с полем ДополнительныеСвойства. В нём подсистема размещает служебные данные модели. Форма для этих расчётов не требуется. Код выполняется на сервере в конфигурации с подсистемой модели состояния; примеры с номенклатурой и договорами используют справочники KASL-demo-ssl.
Переменная СоздатьМодельОбъекта — входной переключатель примеров. Перед выполнением задайте ей Истина, чтобы увидеть заполнение и расчёт отдельными шагами, либо Ложь, чтобы вызвать объединяющий их метод ЗаполнитьИРассчитать. Описание зависимостей и ожидаемый результат в обоих вариантах одинаковы.
Описание простого объекта
Задача. Рассчитать сумму по цене и количеству, не задавая порядок присваиваний зависимым полям в вызывающем коде.
Объект содержит три поля: Цена, Количество и Сумма. Для суммы объявлены две входные зависимости и выражение умножения. В шаблон заполнения передаются только исходные значения: цена 10 и количество 2. Сумму должна определить модель.
Листинг 13 — Расчёт суммы по цене и количеству
// Параметры:
// СоздатьМодельОбъекта - Булево - Истина - разложить этап расчета на шаги: Получение модели объекта, Конвертация шаблона заполнения в массив параметров состояния, расчет нового состояния
Объект = Новый Структура;
Объект.Вставить("Сумма");
Объект.Вставить("Цена");
Объект.Вставить("Количество");
Объект.Вставить("ДополнительныеСвойства", Новый Структура);
РаботаСМодельюСостояния.НоваяМодельСостояния(Объект)
.ПараметрСостояния("Сумма")
.Параметр("Цена")
.Параметр("Количество")
.Выражение("Параметры.Цена * Параметры.Количество")
.ПрименитьМодель()
;
ШаблонОбъекта = Новый Структура;
ШаблонОбъекта.Вставить("Количество", 2);
ШаблонОбъекта.Вставить("Цена", 10);
Если СоздатьМодельОбъекта Тогда
МодельОбъекта = РаботаСОбъектомКлиентСервер.НоваяМодельОбъекта(Объект);
ИзмененныеПараметры = РаботаСОбъектомКлиентСервер.ЗаполнитьОбъект(МодельОбъекта, ШаблонОбъекта);
СостояниеРасчета = РаботаСОбъектомКлиентСервер.Рассчитать(Объект, ИзмененныеПараметры);
Иначе
РаботаСОбъектом.ЗаполнитьИРассчитать(Объект, ШаблонОбъекта);
КонецЕсли;
Сообщить(СтрШаблон("%1 х %2 = %3", Объект.Цена, Объект.Количество, Объект.Сумма));
В развёрнутом варианте НоваяМодельОбъекта предоставляет доступ к данным и применённой схеме. ЗаполнитьОбъект переносит значения шаблона и возвращает изменённые параметры. Рассчитать получает этот список и обрабатывает зависимую сумму. В кратком варианте те же этапы выполняются внутри ЗаполнитьИРассчитать.
Ожидаемый результат: цена равна 10, количество — 2, сумма — 20; сообщение выводит 10 х 2 = 20. Для следующего расчёта можно передать новый шаблон только с изменившимся количеством: существующее описание модели уже содержит правило пересчёта суммы.
Объект с табличной частью
Задача. Рассчитать суммы строк и общий итог, а также сравнить изменение отдельной строки с полным перезаполнением таблицы.
Объект содержит таблицу Товары и поле СуммаДокумента. До заполнения в таблице находятся две строки с ценой 4, количеством 10 и суммой 40. В модели описаны два уровня зависимостей: Товары.Сумма рассчитывается по цене и количеству своей строки, а СуммаДокумента — по итогу колонки Сумма всей таблицы.
Перед запуском задайте МодификацияСтрок. Значение Истина выбирает изменение существующей строки по её идентификатору, Ложь — замену содержимого таблицы строками шаблона.
Листинг 14 — Расчёт сумм строк и итога по табличной части
// Параметры:
// СоздатьМодельОбъекта - Булево - Истина - разложить этап расчета на шаги: Получение модели объекта, Конвертация шаблона заполнения в массив параметров состояния, расчет нового состояния
// МодификацияСтрок - Булево - Истина - пример с модификацией строк, Ложь - перезаполнение
// Табличная часть объекта
Товары = Новый ТаблицаЗначений();
Товары.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка._ДемоНоменклатура"));
Товары.Колонки.Добавить("Цена", ОбщегоНазначения.ОписаниеТипаЧисло(10, 3));
Товары.Колонки.Добавить("Количество", ОбщегоНазначения.ОписаниеТипаЧисло(9));
Товары.Колонки.Добавить("Сумма", ОбщегоНазначения.ОписаниеТипаЧисло(15, 3));
// Моделируемый объект
Объект = Новый Структура;
Объект.Вставить("СуммаДокумента");
Объект.Вставить("Товары", Товары);
Объект.Вставить("ДополнительныеСвойства", Новый Структура); // Обязательно должно быть у моделируемого объекта
// Строка 1
СтрокаТовары = Товары.Добавить();
СтрокаТовары.Цена = 4;
СтрокаТовары.Количество = 10;
СтрокаТовары.Сумма = 40;
// Строка 2
СтрокаТовары = Товары.Добавить();
СтрокаТовары.Цена = 4;
СтрокаТовары.Количество = 10;
СтрокаТовары.Сумма = 40;
// Модель состояния DSL
РаботаСМодельюСостояния.НоваяМодельСостояния(Объект)
.ПараметрСостояния("СуммаДокумента")
.Параметр("Товары")
.Выражение("Параметры.Товары.Итог(""Сумма"")")
.ПараметрСостояния("Товары")
.ПараметрСостояния("Товары.Сумма")
.Параметр("Товары.Цена")
.Параметр("Товары.Количество")
.Выражение("Параметры.Товары.Цена * Параметры.Товары.Количество")
.ПрименитьМодель()
;
// Заполнение объекта по образцу
ШаблонОбъекта = Новый Структура;
ШаблонОбъекта.Вставить("Товары", Новый Массив);
Если МодификацияСтрок Тогда
ШаблонОбъекта.Товары.Добавить(Новый Структура("Цена, Количество, ИдентификаторСтроки", 10, 2, 1));
Иначе
ШаблонОбъекта.Товары.Добавить(Новый Структура("Цена, Количество", 10, 2));
ШаблонОбъекта.Товары.Добавить(Новый Структура("Цена, Количество", 100, 2));
КонецЕсли;
// Расчет объекта
Если СоздатьМодельОбъекта Тогда
МодельОбъекта = РаботаСОбъектомКлиентСервер.НоваяМодельОбъекта(Объект);
ИзмененныеПараметры = РаботаСОбъектомКлиентСервер.ЗаполнитьОбъект(МодельОбъекта, ШаблонОбъекта);
СостояниеРасчета = РаботаСОбъектомКлиентСервер.Рассчитать(Объект, ИзмененныеПараметры);
Иначе
РаботаСОбъектом.ЗаполнитьИРассчитать(Объект, ШаблонОбъекта);
КонецЕсли;
В этом примере таблица — обычная ТаблицаЗначений, поэтому ИдентификаторСтроки обозначает индекс с нуля. Значение 1 в шаблоне адресует вторую строку. В контексте формы для той же операции используется идентификатор строки данных формы, а не её индекс.
| Режим | Что изменится | Ожидаемый результат |
|---|---|---|
МодификацияСтрок = Истина |
Во второй строке цена станет 10, количество — 2; первая строка сохранится |
Суммы строк 40 и 20, СуммаДокумента = 60 |
МодификацияСтрок = Ложь |
Таблица будет перезаполнена двумя строками без идентификаторов | Суммы строк 20 и 200, СуммаДокумента = 220 |
Режим заполнения определяется по наличию ИдентификаторСтроки у первой строки непустого шаблона. Поэтому в одном массиве следует использовать один способ адресации. При заполнении механизм регистрирует изменения полей строк и самой таблицы. Зависимость итога от Товары связывает расчёт отдельных строк с пересчётом суммы документа.
Связь по значению
Задача. Подобрать договор по организации и контрагенту, дополнив условия переменных параметров фиксированными значениями.
Входные переменные Организация и Контрагент должны содержать ссылки на элементы справочников _ДемоОрганизации и _ДемоКонтрагенты. Поле ДоговорКонтрагента изначально содержит пустую ссылку. Для воспроизводимого результата подготовьте договор выбранных организации и контрагента с номером 144, не помеченный на удаление.
В описании модели Параметр связывает условие отбора с другим параметром состояния: Отбор.Организация получает значение организации, Отбор.Владелец — контрагента. Метод Значение задаёт постоянное условие — номер договора. Дополнительный вызов Связь показывает задание постоянного значения через общий модуль: у связи нет исходного параметра, а в условие Отбор.ПометкаУдаления передаётся Ложь.
Листинг 15 — Подбор договора по отбору с постоянным условием
// Параметры:
// Организация - СправочникСсылка.Организации
// Контрагент - СправочникСсылка.Контрагенты
// СоздатьМодельОбъекта - Булево - Истина - разложить этап расчета на шаги: Получение модели объекта, Конвертация шаблона заполнения в массив параметров состояния, расчет нового состояния
Объект = Новый Структура;
Объект.Вставить("Организация", Справочники._ДемоОрганизации.ПустаяСсылка());
Объект.Вставить("Контрагент", Справочники._ДемоКонтрагенты.ПустаяСсылка());
Объект.Вставить("ДоговорКонтрагента", Справочники._ДемоДоговорыКонтрагентов.ПустаяСсылка());
Объект.Вставить("ДополнительныеСвойства", Новый Структура);
РаботаСМодельюСостояния.НоваяМодельСостояния(Объект)
.ПараметрСостояния("Организация")
.ПараметрСостояния("Контрагент")
.ПараметрСостояния("ДоговорКонтрагента")
.Параметр("Отбор.Организация", "Организация")
.Параметр("Отбор.Владелец", "Контрагент")
.Значение("Отбор.НомерДоговора", "144") // условие на конкретный номер договора
.ПрименитьМодель()
;
ШаблонОбъекта = Новый Структура;
ШаблонОбъекта.Вставить("Организация", Организация);
ШаблонОбъекта.Вставить("Контрагент", Контрагент);
Если СоздатьМодельОбъекта Тогда
МодельОбъекта = РаботаСОбъектомКлиентСервер.НоваяМодельОбъекта(Объект);
ИзмененныеПараметры = РаботаСОбъектомКлиентСервер.ЗаполнитьОбъект(МодельОбъекта, ШаблонОбъекта);
СостояниеРасчета = РаботаСОбъектомКлиентСервер.Рассчитать(Объект, ИзмененныеПараметры);
Иначе
РаботаСОбъектом.ЗаполнитьИРассчитать(Объект, ШаблонОбъекта);
КонецЕсли;
ОбщегоНазначения.СообщитьПользователю("Договор: " + Объект.ДоговорКонтрагента, Объект.ДоговорКонтрагента);
Ожидаемый результат: если условиям соответствует ровно один договор, его ссылка попадёт в ДоговорКонтрагента. Если подходящих договоров нет или их несколько, изначально пустое поле останется пустым. Номер 144 здесь является условием поиска и сам по себе не гарантирует уникальности результата.
При дальнейшем изменении организации или контрагента модель проверяет уже выбранный договор на соответствие новым условиям. Подходящее значение сохраняется, неподходящее очищается; пустое значение может быть заполнено однозначно найденным договором. Так связь описывает и допустимость значения, и его автоматический подбор.
Работа с формой
Для примеров работы с формой я разработал специальную консоль, в которой можно моделировать форму с ее элементами и реквизитами. В этих примерах всегда идет вначале код построения формы, а затем ее модели. В качестве построителя форм я использовал разработку автора Никита Иванченко formhelper.
Демо модель состояния

Рисунок 7 — Обработка _ДемоМодельСостояния с текстовыми макетами примеров
Примеры взяты из текстовых макетов обработки _ДемоМодельСостояния проекта KASL-demo-ssl. Откройте её форму Консоль и выберите пример: обработка загрузит текст макета и выполнит его на сервере. После изменения текста команда применения модели заново создаёт тестовую форму.
Каждый макет содержит создание реквизитов и элементов через РаботаСФормамиСервер.НовыйПостроительФорм, описание модели и, при необходимости, начальное заполнение. Для запуска требуется подключённое расширение formhelper, предоставляющее построитель форм. Служебные обработчики событий и процедура ОбновитьФормуНаСервере уже находятся в модуле консоли.
Ниже первый пример приведён вместе с построением полей. В остальных показаны ключевые фрагменты макетов; состав необходимых реквизитов и элементов описан перед кодом. Полные макеты можно запускать независимо друг от друга. При переносе в собственную форму сначала подключите служебный код из раздела «Внедрение подсистемы в форму», затем создайте указанные реквизиты и элементы.
Связи параметров выбора
Источник: макет СвязиПараметровВыбора.
Задача. Ограничить выбор договора текущими организацией и контрагентом. Это тот же принцип отбора, что и в примере с объектом, но теперь условия также используются при взаимодействии пользователя с полем ввода.
Макет создаёт три ссылочных реквизита формы и связанные с ними поля. Для договора объявляются две зависимости:
Листинг 16 — Связи параметров выбора договора
// Создание реквизитов и элементов тестовой формы
РаботаСФормамиСервер.НовыйПостроительФорм(ЭтаФорма).Группа(Элементы.ТестоваяФорма)
.РеквизитСсылка("Организация", "СправочникСсылка._ДемоОрганизации")
.РеквизитСсылка("Контрагент", "СправочникСсылка._ДемоКонтрагенты")
.РеквизитСсылка("ДоговорКонтрагента", "СправочникСсылка._ДемоДоговорыКонтрагентов")
.ПолеВвода("Организация")
.ПутьКДанным("Организация")
.ПолеВвода("Контрагент")
.ПутьКДанным("Контрагент")
.ПолеВвода("ДоговорКонтрагента")
.ПутьКДанным("ДоговорКонтрагента")
.Применить()
;
// Создание модели состояния формы
МодельСостояния = РаботаСМодельюСостояния.НоваяМодельСостояния(ЭтотОбъект)
.ПараметрСостояния("ДоговорКонтрагента")
.Параметр("Отбор.Организация", "Организация")
.Параметр("Отбор.Владелец", "Контрагент")
.ПрименитьМодель()
;
Первый аргумент Параметр задаёт имя условия, второй — источник его значения. Разработчику не требуется отдельно повторять эти условия в обработчике начала выбора договора: подсистема использует объявленные связи для настройки поля.
Что проверить. Выберите организацию и контрагента, затем откройте выбор договора. Доступные договоры должны соответствовать обоим условиям. Если допустим только один договор, модель может заполнить его автоматически. Смените контрагента: договор прежнего владельца должен перестать быть допустимым, а настройки выбора — учитывать нового контрагента.
Контекстные параметры и видимость группы
Источник: макет КонтекстныеПараметрыСостояния.
Задача. Скрывать поля контрагента и договора для внутренних операций, описав условие через промежуточные параметры состояния.
К предыдущему составу реквизитов добавляется ВидОперации типа СправочникСсылка._ДемоВидыОперацийБюджетирование. Поля контрагента и договора помещаются в группу ГруппаКонтрагент. В модели сначала определяется тип выбранной операции, затем — логический признак внутренней операции. Фрагмент из макета показывает эту цепочку и её связь с видимостью группы:
Листинг 17 — Видимость группы по цепочке параметров состояния
МодельСостояния = РаботаСМодельюСостояния.НоваяМодельСостояния(ЭтотОбъект)
.ПараметрСостояния("ТипОперации")
.Параметр("ВидОперации")
.Выражение("ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Параметры.ВидОперации, 'ТипОперацииБюджетирование')")
.ПараметрСостояния("ЭтоВнутренняяОперация").ТипБулево()
.Параметр("ТипОперации")
.Выражение("(Ложь
| ИЛИ Параметры.ТипОперации = ПредопределенноеЗначение('Перечисление._ДемоТипыОперацийБюжетирование.ПеремещениеМеждуМестамиХраненияСредств')
| ИЛИ Параметры.ТипОперации = ПредопределенноеЗначение('Перечисление._ДемоТипыОперацийБюжетирование.КонвертацияВалюты')
|)")
.ПараметрСостояния("ДоговорКонтрагента")
.Параметр("Отбор.Организация", "Организация")
.Параметр("Отбор.Владелец", "Контрагент")
.ЭлементФормы("ГруппаКонтрагент")
.Свойство("Видимость")
.Параметр("ЭтоВнутренняяОперация")
.Выражение("НЕ Параметры.ЭтоВнутренняяОперация")
.ПрименитьМодель()
;
ТипОперации и ЭтоВнутренняяОперация не создаются построителем как реквизиты формы: это вспомогательные параметры модели. Они позволяют один раз определить смысл выбранной операции и использовать результат в правилах интерфейса. В полном макете дополнительно объявлены ЭтоВГО и ЭтоВнешняяОперация; в приведённую цепочку видимости они не входят.
Что проверить. Выберите вид операции с типом ПеремещениеМеждуМестамиХраненияСредств или КонвертацияВалюты: группа контрагента должна скрыться. Выберите вид с другим типом — группа должна появиться. Пересчёт проходит по цепочке ВидОперации → ТипОперации → ЭтоВнутренняяОперация → Видимость. Само скрытие группы не является командой очистки её реквизитов.
Заголовок и режим просмотра формы
Источник: макет СвойстваФормы.
Задача. Формировать заголовок по данным и состоянию объекта, а возможность редактирования — по признаку проведения.
Макет дополняет форму реквизитами Дата, Номер и логическими признаками Проведен, ПометкаУдаления, ЭтоНовый. Для даты, номера и признака проведения создаются поля. Следующий фрагмент добавляется к модели до её применения:
Листинг 18 — Заголовок и режим просмотра формы
МодельСостояния.ЭлементФормы()
.Свойство("Заголовок")
.Параметр("Дата")
.Параметр("Номер")
.Параметр("Проведен")
.Параметр("ПометкаУдаления")
.Параметр("ЭтоНовый")
.Выражение("СтрШаблон('Заявка на операцию %1 от %2. %3', Параметры.Номер, Параметры.Дата,
| ?(Параметры.Проведен, 'Проведен',
| ?(Параметры.ПометкаУдаления, 'Помечен на удаление',
| ?(Параметры.ЭтоНовый, 'Новый', 'Записан')
| )
| )
|)
|")
.Свойство("ТолькоПросмотр")
.Параметр("Проведен")
.Выражение("Параметры.Проведен")
;
МодельСостояния.ПрименитьМодель();
Вызов ЭлементФормы() без имени адресует всю форму. Статус в заголовке определяется с приоритетом: сначала проведение, затем пометка удаления, затем признак нового объекта. Если ни один признак не установлен, выводится Записан.
После применения модели макет задаёт начальные данные, запускает расчёт и обновляет интерфейс:
Листинг 19 — Начальные данные, расчёт и обновление формы
ЭтотОбъект.ЭтоНовый = Ложь;
ЭтотОбъект.Проведен = Истина;
ЭтотОбъект.Дата = ТекущаяДатаСеанса();
// Полный расчет
СостояниеРасчета = РаботаСОбъектомКлиентСервер.Рассчитать(ЭтотОбъект);
// Рендеринг
ОбновитьФормуНаСервере(СостояниеРасчета.РассчитанныеПараметры);
Ожидаемый результат: заголовок содержит текущую дату и статус Проведен, а форма переходит в режим просмотра. Номер в исходном макете не заполняется. Признак Проведен здесь — тестовый реквизит: его установка демонстрирует реакцию интерфейса, а не выполняет проведение документа.
Чтобы сравнить состояния, измените начальное значение Проведен в тексте макета на Ложь и повторно примените пример. Форма станет редактируемой; изменение даты и номера будет менять заголовок. Важна последовательность: программные присваивания подготавливают данные, явный вызов Рассчитать запускает зависимости, а ОбновитьФормуНаСервере применяет результат к интерфейсу.
Обновление формы после заполнения по шаблону
Источник: макет ИзменениеСостоянияФормы.
Задача. Передать программные изменения модели и обновить связанные свойства формы по результату расчёта.
Состав реквизитов и правила заголовка и режима просмотра такие же, как в предыдущем примере. Отличается способ передачи исходных данных: вместо прямых присваиваний макет использует шаблон заполнения.
Листинг 20 — Передача изменений через шаблон заполнения
// Шаблон
ШаблонОбъекта = Новый Структура;
ШаблонОбъекта.Вставить("Дата", ТекущаяДата());
ШаблонОбъекта.Вставить("Номер", "00001");
ШаблонОбъекта.Вставить("Проведен", Истина);
// Заполнение данных объекта из шаблона
МодельОбъекта = РаботаСОбъектомКлиентСервер.НоваяМодельОбъекта(ЭтотОбъект);
ИзмененныеПараметры = РаботаСОбъектомКлиентСервер.ЗаполнитьОбъект(МодельОбъекта, ШаблонОбъекта);
// Расчет + рендеринг
ПриИзмененииНаСервере(ИзмененныеПараметры);
ЗаполнитьОбъект возвращает параметры, изменившиеся при переносе значений. Этот список становится входом расчёта. Для обновления формы используется уже другой список — РассчитанныеПараметры, возвращённый расчётным механизмом. Он связывает изменения данных с зависимыми свойствами интерфейса.
Ожидаемый результат: в заголовке появляется номер 00001, текущая дата и статус Проведен; форма становится доступной только для просмотра. Чтобы получить другой результат, достаточно изменить данные шаблона, например передать Проведен = Ложь. Правила заголовка и доступности при этом остаются прежними.
Табличная часть, заголовки страниц и текущая строка
Источник: макет ФормаСТабличнойЧастью.
Задача. Объединить расчёт строк с отображением состояния таблицы: количеством строк в заголовке страницы и ценой выбранной строки в отдельной надписи.
Макет создаёт таблицу Товары с колонками Цена, Количество, Сумма, Отметка и таблицу ДвиженияОперации с колонкой СтатьяДДС. Они размещаются на страницах СтраницаТовары и СтраницаДвиженияОперации. Под товарами находится надпись ТоварыТекущийЭлемент. Модель связывает эти элементы с данными:
Листинг 21 — Заголовки страниц и надпись текущей строки
МодельСостояния = РаботаСМодельюСостояния.НоваяМодельСостояния(ЭтотОбъект)
.ПараметрСостояния("Товары").НаКлиенте()
.ПараметрСостояния("ДвиженияОперации").НаКлиенте()
.ПараметрСостояния("Товары.Сумма").НаКлиенте()
.Параметр("Цена", "Товары.Цена")
.Параметр("Количество", "Товары.Количество")
.Выражение("Параметры.Цена * Параметры.Количество")
.ПараметрСостояния("КоличествоДвиженийОперации").НаКлиенте()
.Параметр("ДвиженияОперации").ПроверкаЗаполнения(Ложь)
.Выражение("Параметры.ДвиженияОперации.Количество()")
.ЭлементФормы("Товары")
.ТекущаяСтрока("Таблицы.Товары.ТекущаяСтрока")
.ЭлементФормы("СтраницаТовары")
.Свойство("Заголовок")
.Параметр("Товары")
.Выражение("СтрШаблон('Товары (%1)', Параметры.Товары.Количество())")
.ЭлементФормы("СтраницаДвиженияОперации")
.Свойство("Заголовок")
.Параметр("КоличествоДвиженийОперации")
.Выражение("СтрШаблон('Аналитика (%1)', Параметры.КоличествоДвиженийОперации)")
.ЭлементФормы("ТоварыТекущийЭлемент")
.Свойство("Заголовок")
.Параметр("Товары")
.Параметр("Таблицы.Товары.ТекущаяСтрока")
.Выражение("?(Параметры.Таблицы.Товары.ТекущаяСтрока = Неопределено, '-', СтрШаблон('Цена: %1', Параметры.Товары.НайтиПоИдентификатору(Параметры.Таблицы.Товары.ТекущаяСтрока).Цена))")
.ПрименитьМодель()
;
Здесь показаны два способа расчёта заголовка страницы. Для товаров количество строк получается непосредственно из таблицы. Для движений оно вынесено в отдельный параметр КоличествоДвиженийОперации. В его зависимости отключена проверка заполнения: пустая таблица допустима, и выражение должно вернуть 0.
Надпись зависит и от текущей строки, и от таблицы. Первая связь нужна при переходе между строками, вторая — при изменении их данных. Метод НайтиПоИдентификатору получает строку по идентификатору данных формы. Если текущей строки нет, выражение возвращает -.
В конце макета таблица товаров переводится в режим просмотра и заполняется через модель:
Листинг 22 — Начальное заполнение таблицы и рендеринг
// Переводит таблицу в режим открытия элементов
Элементы.Товары.ТолькоПросмотр = Истина;
// Шаблон
ШаблонОбъекта = Новый Структура;
ШаблонОбъекта.Вставить("Товары", Новый Массив);
ШаблонОбъекта.Товары.Добавить(Новый Структура("Цена, Количество", 5, 100));
ШаблонОбъекта.Товары.Добавить(Новый Структура("Цена, Количество", 3, 50));
ШаблонОбъекта.Товары.Добавить(Новый Структура("Цена, Количество", 4, 10));
// Заполнение данных объекта из шаблона
МодельОбъекта = РаботаСОбъектомКлиентСервер.НоваяМодельОбъекта(ЭтотОбъект);
ИзмененныеПараметры = РаботаСОбъектомКлиентСервер.ЗаполнитьОбъект(МодельОбъекта, ШаблонОбъекта);
// Расчет + рендеринг
ПриИзмененииНаСервере(ИзмененныеПараметры);
Ожидаемый результат: суммы трёх строк равны 500, 150 и 40, заголовок первой страницы — Товары (3), второй — Аналитика (0). При выборе строк надпись показывает соответственно Цена: 5, Цена: 3 и Цена: 4.
В исходном макете товары закрыты для ручного редактирования. Для проверки пересчёта при вводе замените установку ТолькоПросмотр на Ложь и повторно примените пример. Измените цену или количество, затем завершите редактирование строки: сумма должна пересчитаться. Добавление или удаление строки должно изменить количество в заголовке страницы.
Отбор в подчинённой таблице
Источник: макет ПодчиненнаяТаблица.
Задача. Показывать в таблице движений только строки, относящиеся к выбранному товару, и рядом выводить его цену.
В таблице Товары появляется числовая колонка ID, а в ДвиженияОперации — ID_Номенклатуры. Их значения связывают строки двух таблиц. Для вывода цены создаётся надпись ЦенаНоменклатуры. Правило расчёта суммы строки остаётся тем же, что в предыдущем примере; ниже показана часть модели, отвечающая за выбор и отображение:
Листинг 23 — Отбор в подчинённой таблице по текущей строке
МодельСостояния.ЭлементФормы("Товары")
.ТекущаяСтрока()
.ЭлементФормы("ДвиженияОперации")
.Свойство("ОтборСтрок")
.Параметр("Товары")
.Параметр("Таблицы.Товары.ТекущаяСтрока")
.Выражение("?(Параметры.Таблицы.Товары.ТекущаяСтрока = Неопределено, Неопределено, Новый ФиксированнаяСтруктура('ID_Номенклатуры', Параметры.Товары.НайтиПоИдентификатору(Параметры.Таблицы.Товары.ТекущаяСтрока).ID))")
.ЭлементФормы("ЦенаНоменклатуры")
.Свойство("Заголовок")
.Параметр("Товары")
.Параметр("Таблицы.Товары.ТекущаяСтрока")
.Выражение("?(Параметры.Таблицы.Товары.ТекущаяСтрока = Неопределено, '-', СтрШаблон('Цена: %1', Параметры.Товары.НайтиПоИдентификатору(Параметры.Таблицы.Товары.ТекущаяСтрока).Цена))")
.ПрименитьМодель()
;
ТекущаяСтрока() без аргумента создаёт параметр со стандартным именем Таблицы.Товары.ТекущаяСтрока. Сначала по его значению находится строка товаров, затем из неё читается прикладной ID. Именно этот ID, а не внутренний идентификатор строки формы, передаётся в отбор по ID_Номенклатуры.
Начальные данные макета состоят из трёх товаров с ID от 1 до 3, ценами 5, 3, 4 и количествами 100, 50, 10. Для каждого товара создаётся одна строка движений с соответствующим ID_Номенклатуры. Статьи ДДС подбираются по кодам 000000001, 000000002 и 000000003; для отображения заполненных ссылок эти элементы должны присутствовать в демо-базе. Обе таблицы заполняются одним шаблоном, после чего вызываются расчёт и обновление формы.
Что проверить. Выберите товар с ID = 2: в подчинённой таблице должна остаться видимой строка с ID_Номенклатуры = 2, а надпись должна показать Цена: 3. При выборе первого или третьего товара отбор и надпись должны измениться вместе.
Если текущая строка не выбрана, выражение возвращает Неопределено для ОтборСтрок и - для надписи. В данном макете это означает снятие отбора, а не требование скрыть все движения. Отбор управляет отображением существующих строк: он не удаляет остальные строки и не заполняет ID_Номенклатуры при добавлении новой. Автозаполнение по отбору относится к развитию механизма, описанному выше.
Таким образом, выбор строки становится таким же входным параметром модели, как цена, количество или вид операции. Прикладной код описывает зависимости между данными и интерфейсом, а общие обработчики формы передают изменения в расчёт и применяют его результат.
Демо Заявка на операцию
Заявка на операцию

Рисунок 8 — Форма документа _ДемоЗаявкаНаОперацию
Документ _ДемоЗаявкаНаОперацию из KASL-demo-ssl объединяет несколько сценариев, которые по отдельности разобраны в практикуме: заполнение объекта, зависимости реквизитов, отборы для ссылок, расчёт строк таблицы и состояние интерфейса. Это пример документа, для которого одни и те же правила расчёта данных используются при создании объекта и при работе с его формой.
Общая модель документа и дополнение формы
Функция МодельСостояния(Контекст) модуля менеджера создаёт общую модель. В ней находятся зависимости, не требующие интерфейса: тип операции определяется по виду операции, договор ограничивается организацией и контрагентом, сумма документа зависит от суммы взаиморасчётов и курса. Например:
Листинг 24 — Фрагмент общей модели документа
МодельСостояния = РаботаСМодельюСостояния.НоваяМодельСостояния(Контекст, , "_ДемоЗаявкаНаОперациюМодель")
.ПараметрСостояния("ДоговорКонтрагента")
.НеИспользование("ЭтоРасчетыБезДоговора")
.Параметр("Отбор.Организация", "Организация")
.Параметр("Отбор.Владелец", "Контрагент")
.ПараметрСостояния("СуммаДокумента").НаКлиенте()
.Параметр("СуммаВзаиморасчетов")
.Параметр("КурсРасчетов")
.Выражение("Параметры.СуммаВзаиморасчетов * Параметры.КурсРасчетов")
;
Это фрагмент общей схемы, а не полное описание документа: в полной модели признак ЭтоРасчетыБезДоговора также рассчитывается по виду операции, а для суммы заданы её исходные параметры. Связи Отбор.* позволяют проверять выбранный договор и настраивать его выбор по актуальным реквизитам.
При создании документа ОбработкаЗаполнения строит шаблон с безналичной формой оплаты, расходом, видом операции «Внутреннее перемещение», курсом 1 и суммой взаиморасчётов 100. Затем применяет общую модель к объекту и вызывает РаботаСОбъектом.ЗаполнитьИРассчитать. Из исходных данных следует сумма документа 100 × 1 = 100 — её не приходится записывать в шаблон отдельно. В этом сценарии объект рассчитывается без открытия формы.
Форма получает ту же модель через Документы._ДемоЗаявкаНаОперацию.МодельСостояния(ЭтотОбъект) и дополняет её правилами взаимодействия. В ПрименитьМодельСостояния описаны свойства элементов, вспомогательные параметры аналитики строк и обработчик инициализации. При создании формы сначала вызывается ПрименитьМодельСостояния(), затем ОбновитьФормуНаСервере(). Таким образом, схема расчёта документа остаётся общей, а описание его представления добавляется только при наличии формы.
Что можно проверить в форме
Откройте новый документ «Демо заявка на операцию» в базе с данными KASL-demo-ssl. Если в базе есть вид операции «Внутреннее перемещение», начальное заполнение выбирает именно его. Далее можно пройти несколько независимых сценариев:
- Вид операции. Общая модель рассчитывает
ТипОперации,ЭтоВнутренняяОперацияиЭтоВнешняяОперация. Для внутренней операции форма показывает группу конвертации, скрывает группу реквизитов контрагента и таблицуДвиженияОперации. Выберите вид внешней операции: модель изменит признаки, а форма покажет реквизиты контрагента и таблицу. Заголовки сторон зависят также от направленияПриходРасход: организация может выступать плательщиком или получателем. - Договор и валюта. При заполнении организации и контрагента выбор договора ограничивается обоими значениями. Валюта взаиморасчётов зависит от валюты договора. Связь от договора к валюте помечена как
Слабая: это позволяет отдельно выбрать валюту в интерфейсе. Для такого ручного изменения у формы есть специальный обработчик: на сервере он ищет договор по владельцу, организации и валюте. Если найден ровно один вариант, договор меняется черезЗаполнитьи запускается пересчёт; если нет — передаётсяНеопределено. Это прикладной алгоритм обратного подбора, а не ещё одно ребро «валюта → договор» в общей схеме. - Суммы и отображение. Измените
СуммаВзаиморасчетовилиКурсРасчетов: общая модель пересчитываетСуммаДокумента. ЕслиВалютаВзаиморасчетовиВалютаДокументаразличаются, форма показывает поле суммы взаиморасчётов и меняет видимость соответствующей колонки таблицы. Заголовки колонок сумм содержат обозначение заполненной валюты. Это два связанных эффекта изменения данных: расчёт реквизитов документа и обновление свойств элементов формы. - Строки аналитики. Для таблицы
ДвиженияОперациизадан ключОбъектИнвестирования, СтатьяДДС. При изменении статьи ДДС подключённый обработчик модели получает вид аналитики, тип её значения и наименование и заполняет вспомогательные поля этой же строки по её идентификатору. Эти данные используются формой для настройки поляАналитика1; возможность его использования зависит от наличия вида аналитики. Инициализатор дополнительно подготавливает сведения для уже существующих строк при открытии формы.
Вот как правило данных и правило отображения для суммы разведены по разным местам. В менеджере описана формула суммы документа, а в форме — только видимость поля:
Листинг 25 — Правило видимости поля суммы в форме
.ЭлементФормы("СуммаВзаиморасчетов")
.Свойство("Видимость")
.Параметр("ВалютаВзаиморасчетов")
.Параметр("ВалютаДокумента")
.Выражение("Параметры.ВалютаВзаиморасчетов <> Параметры.ВалютаДокумента")
Общий обработчик ПриИзменении(Элемент) переводит событие поля в изменение параметра модели. ПриИзмененииНаКлиенте запускает расчёт; если встречается серверный параметр, передаёт незавершённое состояние в РассчитатьНаСервере. После расчёта ОбновитьФорму… применяет изменённые свойства. Разработчик объявляет зависимости и необходимые прикладные обработчики, а не выстраивает всю последовательность обновлений отдельно для каждого поля.
В форме есть также демонстрационные команды «Изменить данные объекта», «Обновить данные» и «Проверить заполнение». Первая программно задаёт дату и валюту документа через шаблон, затем пересчитывает модель и обновляет интерфейс. Вторая ставит параметры модели в очередь для повторного расчёта. Третья обходит параметры и строки таблицы, проверяет признаки Использование и ПроверкаЗаполнения и сообщает о пустых значениях. Пересчёт зависимостей и проверка готовности документа к использованию — разные операции.
Выбор договора, видов операций и статей ДДС зависит от справочников демонстрационной базы. Если подходящих элементов нет, зависимые значения могут остаться незаполненными; это не меняет объявленные правила. Раздел описывает поведение по исходникам демо, без проверки сценариев в работающей базе.
Демо Звездный рейтинг
Звездный рейтинг

Рисунок 9 — Обработка ЗвездныйРейтинг
Обработка ЗвездныйРейтинг показывает минимальную модель состояния интерфейса. У объекта есть один числовой реквизит Рейтинг; в форме находятся десять кликабельных декораций — пять заполненных и пять пустых звёзд. Картинки берутся из стандартных изображений платформы. Пары декораций не заменяются и не перекрашиваются: модель меняет их свойство Видимость и заголовок формы по одному исходному значению.
Описание состояния звёзд
При создании формы применяется модель. В ней Рейтинг помечен как клиентский параметр, а каждое свойство зависит от него. Ниже приведён фрагмент из ПриСозданииНаСервере:
Листинг 26 — Описание состояния звёзд в модели
РаботаСМодельюСостояния.НоваяМодельСостояния(ЭтотОбъект, , "Контекст")
.ЭлементФормы()
.Свойство("Заголовок")
.Параметр("Рейтинг")
.Выражение("СтрШаблон('Текущий рейтинг - %1', Параметры.Рейтинг)")
.ЭлементФормы("ВыбраннаяЗвезда3")
.Свойство("Видимость")
.Параметр("Рейтинг")
.Выражение("Параметры.Рейтинг >= 3")
.ЭлементФормы("ПустаяЗвезда3")
.Свойство("Видимость")
.Параметр("Рейтинг")
.Выражение("Параметры.Рейтинг < 3")
.ПараметрСостояния("Рейтинг").НаКлиенте()
.ПрименитьМодель()
;
Здесь показана только третья пара: остальные пары и их условия также объявлены в исходном модуле. Для звёзд со второй по пятую логика симметрична: заполненная видна при рейтинге не меньше номера звезды, пустая — при меньшем. У первой пары условия записаны как Рейтинг >= 1 и Рейтинг = 0, у пятой заполненная проверяет Рейтинг = 5. Это именно условия демо; отдельной проверки диапазона 0…5 для произвольно присвоенного значения модуль не задаёт.
При открытии вызывается ОбновитьФормуНаКлиенте(), поэтому исходное значение реквизита отображается сразу. Для значений, которые выбираются щелчками в этой форме, результат выглядит так:
Рейтинг |
Заполненные звёзды | Пустые звёзды | Заголовок |
|---|---|---|---|
0 |
нет | 1–5 | Текущий рейтинг - 0 |
3 |
1–3 | 4–5 | Текущий рейтинг - 3 |
5 |
1–5 | нет | Текущий рейтинг - 5 |
Нажатие и пересчёт
Каждая декорация имеет обработчик нажатия. Нажатие пустой звезды устанавливает рейтинг, равный её номеру; нажатие заполненной — числу звёзд перед ней. Например, ПустаяЗвезда3Нажатие передаёт 3, а ВыбраннаяЗвезда3Нажатие — 2. Нажатие первой заполненной звезды сбрасывает рейтинг в 0.
Обработчики передают значение одной процедуре ВыбратьРейтинг. Она заполняет параметр через модель, передаёт зарегистрированные изменения в расчёт и после обновления возвращает фокус на декорацию, видимую на месте нажатой звезды:
Листинг 27 — Обработчик выбора рейтинга
&НаКлиенте
Процедура ВыбратьРейтинг(Рейтинг, ИмяЭлемента)
ШаблонОбъекта = Новый Структура;
ШаблонОбъекта.Вставить("Рейтинг", Рейтинг);
ИзмененныеПараметры = РаботаСОбъектомКлиентСервер.Заполнить(ЭтотОбъект, ШаблонОбъекта);
ПриИзмененииНаКлиенте(ИзмененныеПараметры);
// Возврат фокуса на заменившую нажатую звезду декорацию
ЭтоВыбраннаяЗвезда = НЕ СтрНачинаетсяС(ИмяЭлемента, "Выбранная");
РейтингЗвезды = Прав(ИмяЭлемента, 1);
ТекущийЭлемент = Элементы[?(ЭтоВыбраннаяЗвезда, "Выбранная", "Пустая") + "Звезда" + РейтингЗвезды];
КонецПроцедуры
ПриИзмененииНаКлиенте вызывает РаботаСОбъектомКлиентСервер.Рассчитать и передаёт РассчитанныеПараметры в ОбновитьФормуНаКлиенте. Там общий механизм применяет видимость и заголовок; при необходимости он может продолжить обновление на сервере. В самих обработчиках звёзд нет десяти команд изменения картинок: они только выбирают новое значение рейтинга.
Что проверить. Откройте обработку: при исходном 0 должны быть видны пять пустых звёзд. Нажмите третью пустую — заголовок покажет 3, а первые три звезды станут заполненными. Нажмите третью заполненную — рейтинг уменьшится до 2. Нажмите первую заполненную — снова получится 0. Это демонстрирует цикл «действие пользователя → изменение параметра → пересчёт → обновление формы» на примере, где вся визуальная проекция задаётся декларативно.
Состав и установка
Состав
В демонстрационной конфигурации KASL-demo-ssl решение разделено на подсистему механизма KASL → Модели → МодельСостояния и подсистему примеров KASLДемо → _ДемоМодельСостояния. Первая содержит описание модели, расчёт и обновление формы. Вторая показывает применение этих возможностей в документе и обработках; её объекты нужны для воспроизведения примеров статьи, но не для описания собственной модели в прикладной конфигурации.
В состав подсистемы МодельСостояния входят восемь объектов метаданных:
| Часть | Объекты | Назначение |
|---|---|---|
| Описание схемы | Общие модули РаботаСМодельюСостояния, РаботаСМодельюСостоянияКлиентСервер |
Создание параметров, связей и схемы, применение модели и подготовка её данных для расчёта. |
| DSL модели | Обработка МодельСостояния |
Объектная цепочка вызовов ПараметрСостояния, Параметр, Выражение, ЭлементФормы, Свойство, ПрименитьМодель, используемая в примерах. |
| Расчёт объекта | Общие модули РаботаСОбъектом, РаботаСОбъектомКлиентСервер |
Заполнение объекта по шаблону, регистрация изменений и расчёт зависимых параметров, в том числе при работе без формы. |
| Работа с формой | Общие модули РаботаСФормой, РаботаСФормойКлиент, РаботаСФормойКлиентСервер |
Настройка элементов и параметров выбора, обработка событий формы и применение рассчитанных свойств к интерфейсу. |
Название обработки МодельСостояния здесь относится к объекту метаданных, реализующему DSL; его не следует путать с одноимённым понятием — моделью конкретного документа или формы. Прикладной разработчик получает модель через РаботаСМодельюСостояния.НоваяМодельСостояния(...) и описывает её правила вызовами DSL.
Подсистема KASLДемо → _ДемоМодельСостояния содержит примеры и связанные с ними метаданные:
| Тип | Объекты | Для чего используются |
|---|---|---|
| Обработки | _ДемоМодельСостояния, ЗвездныйРейтинг |
Консоль с текстовыми макетами примеров и отдельная форма с пятью звёздами. |
| Документ и модуль обработчиков | _ДемоЗаявкаНаОперацию, общий модуль _ДемоЗаявкаНаОперациюМодель |
Общая модель документа, форма заявки и прикладные обработчики её параметров. |
| Справочники | _ДемоВидыОперацийБюджетирование, _ДемоОбъектыИнвестирования, _ДемоСтатьиДДС |
Виды операций и аналитика строк заявки. |
| Тип аналитики | План видов характеристик ДополнительныеАналитики |
Тип значения поля аналитики строки. |
| Перечисления | _ДемоВидыДенежныхСредств, _ДемоПриходРасход, _ДемоТипыОперацийБюжетирование |
Форма оплаты, направление и тип операции. |
| Регистр сведений | КонтрагентыВСоставеПериметра |
Определение организации контрагента в обработчике демонстрационной модели. |
Это перечень непосредственного содержимого двух подсистем в KASL-demo-ssl, а не список всех ссылок из кода примеров. Так, заявка и текстовые макеты также обращаются к другим объектам демонстрационной базы и к общим подсистемам; один только набор объектов KASLДемо → _ДемоМодельСостояния не образует самостоятельную конфигурацию. Порядок подключения ядра и его зависимостей приведён ниже в разделе «Установка».
Установка
Объединить с файлом конфигурации из Демо-базы:
- Установить режим объединения с приоритетом в файле
- Отметить по подсистемам файла:
- Основная:
- KASL->МодельСостояния
- Зависимости:
- KASL->ОбщегоНазначения
- KASL->ВыборДанных
- KASL->ПроверкаЗаполнения
- KASL->АТДМассив
- Основная:
Второй способ - собрать свою конфигурацию из подсистем с github (ссылки указаны в подсистемах). На github доступны сборки как в cf, так и cfe. Во втором случае можно просто установить расширения подсистем без необходимости вносить изменения в конфигурацию.
Третий способ - установить сборку всех подсистем в виде одного расширения из проекта kasl.
Зависимости
- БСП
- Подсистема KASL->ОбщегоНазначения
- Подсистема KASL->ВыборДанных
- Подсистема KASL->ПроверкаЗаполнения
- Подсистема KASL->АТДМассив
Проект разрабатывается на EDT и выложен в общий доступ на github. Текущая версия платформы 1С 8.3.27.
Вступайте в нашу телеграмм-группу Инфостарт