Архитектура
Скрипт представляет собой исполняемый JavaScript-код, который подписывается на события изменения полей формы и определяет обработчики этих событий. В контексте исполнения скрипта доступен глобальный объект form, предоставляющий методы для обработки событий, загрузки данных и другие вспомогательные методы.
Подписка на изменение полей осуществляется цепочкой вызовов типа form.onChange().action()
. Например:
/* Задать значение поля */ form.onChange(/*...*/).setValue(/*...*/); /* Проверить корректность заполнения */ form.onChange(/*...*/).validate(/*...*/);
Цепочки с обработчиками должны быть объявлены на верхнем уровне скрипта, т.е. не могут находиться внутри других функций. Каждый скрипт должен включать как минимум один обработчик событий.
Скрипт запускается (инициализируется) в момент открытия задачи или заполнения новой формы, а объявленные в нем глобальные переменные будут доступны в процессе редактирования формы между вызовами обработчиков.
Далее рассмотрим кратко все доступные методы глобального объекта form
.
Метод onChange и обработка событий
Метод onChange
глобального объекта form
предназначен для подписки на изменение конкретных полей формы.
interface form { onChange( fieldNames: string[], executeOnLoad: boolean = false ): ChangeHandler }
В метод onChange
передается 1-2 параметра:
- fieldNames — массив строк с названиями или кодами полей, на изменение которых необходимо реагировать. Значения этих полей будут доступны в обработчиках.
- executeOnLoad — опциональный параметр типа boolean (по умолчанию — false), который определяет нужно ли запускать обработчик при открытии формы, т.е. до того как пользователь отредактировал поле.
Поскольку скрипты работают только в браузере и мобильных приложениях, они не запускаются при заполнении формы через Pyrus API или внешний канал интеграции. Поэтому рекомендуется всегда передавать executeOnLoad равным true, чтобы скрипты выполнились при открытии пользователем задачи с уже заполненными полями.
Метод возвращает объект типа ChangeHandler
, в котором доступны различные методы-обработчики, которые будут исполняться при изменении полей.
interface ChangeHandler { setValue( fieldName: string, calc: (state: FormState) => FieldValue ) setValueAsync( fieldName: string, calcAsync: (state: FormState) => Promise<FieldValue> ) setValues( fieldNames: string[], calc: (state: FormState) => FieldValue[] ) setValuesAsync( fieldNames: string[], calcAsync: (state: FormState) => Promise<FieldValue[]> ) setAssignee( calc: (state: FormState) => number | null ) setAssigneeAsync( calcAsync: (state: FormState) => number | null ) validate( fieldName: string, validate: (state: FormState) => { errorMessage: string, canApprove?: boolean, canSave?: boolean, canReject?: boolean } | undefined ) validateAsync( fieldName: string, validateAsync: (state: FormState) => Promise<{ errorMessage: string, canApprove?: boolean, canSave?: boolean, canReject?: boolean } | undefined> ) setFilter( fieldName: string, calcFilter: (state: FormState) => CatalogFilter | FormFilter ) setFilterAsync( fieldName: string, calcFilterAsync: (state: FormState) => Promise<CatalogFilter | FormFilter> ) }
Все методы имеют простую и async-реализацию. Async-методы отличаются тем, что переданная в них функция-обработчик должна вернуть не результат, а Promise (обещание) с результатом. Это необходимо в том случае, если для вычислений используются асинхронные методы загрузки данных. Пример использования обоих вариантов реализации:
form .onChange(['...'], true) .setValue('...', state => { return someValue; }); form .onChange(['...'], true) .setValueAsync('...', async state => { await dataLoad(); return someValue; });
Более подробно про применение методов setValue, setValues, setValueAsync, setValuesAsync, setAssignee и setAssigneeAsync можно прочитать подробнее в статье Редактирование полей. Методы validate, validateAsync описаны в статье Валидация. Методы фильтрации значений полей типа Справочник: setFilter и setFilterAsync — подробнее описаны в статье Фильтрация значений.
Объект состояния формы
В любой из методов-обработчиков необходимо передать функцию, которая принимает первым аргументом состояние формы (аргумент state) и возвращает значение, например:
form /* При изменении полей «Цена» или «Количество», а также при открытии формы будет запускаться обработчик. */ .onChange(['Цена', 'Количество'], true) .setValue('Общая сумма', state => { const [price, quantity] = state.changes; if (!price || !quantity) return null; return price.value * quantity.value; });
Аргумент state — это объект типа FormState, описание которого представлено ниже:
interface FormState { changes: FieldValue[] prev?: FieldValue[] commenter: Person assignee: Person | undefined currentStep: number taskId?: number }
Доступные поля объекта:
- changes — массив значений полей, имена которых были переданы в метод
onChange
. Значения приходят в том же порядке, в котором поля были перечислены при вызове onChange; - prev — массив текущих значений вычисляемых полей.
- commenter — пользователь, который редактирует форму;
- assignee — ответственный по задаче, есть указан;
- currentStep — текущий этап задачи. При заполнении новой формы — 0;
- taskId — номер текущей задачи. При заполнении новой формы не передается.
Методы загрузки данных
Скрипты могут загружать для расчетов дополнительные данные: реестры форм, элементы справочников, списки ролей. Для этого в глобальном объекте form
доступны следующие методы:
- fetchSelfRegister;
- fetchRegister;
- getCatalog;
- fetchRoles.
Уровни доступа к данным при использовании этих методов определяются уровнями доступа пользователя, у которого запускается скрипт.
Методы загрузки реестра: fetchRegister
и fetchSelfRegister
— описаны в статье Реестр формы.
Важно: В клиентских скриптах запрещены прямые http-запросы как к Pyrus, так и к сторонним сервисам. В случае необходимости использования внешних сервисов или Pyrus API для загрузки данных рассмотрите использование серверных скриптов или обычных ботов.
Загрузка справочников
interface form { getCatalog( catalogName: string | number ): Promise<CatalogItem[]>; } interface CatalogItem { item_id: number; columns: { [name: string]: string; }; }
Метод getCatalog принимает первым аргументом название (строку) или ID (число) справочника и возвращает Promise с массивом элементов справочника. Каждый элемент справочника представляет собой объект с полями:
- item_id — уникальный id элемента справочника;
- columns — объект, в котором ключи соответствуют названиям колонок справочника, а значения — значениям колонок.
Подсказка: в getCatalog необходимо передавать названием именно справочника, а не поля в форме. Например, в форме может использоваться поле типа Справочник под названием Страны и регионы, к которому привязан сам справочник Страны. В этом случает скрипт должен обращаться к объекту под названием Страны.
Загрузка ролей
Метод fetchRoles позволяет получить список ролей пользователя или полный список ролей организации.
interface form { fetchRoles(personId?: number): Promise<RoleItem[]>; } interface RoleItem { role_id: number, role_name: string, members_ids: number[], }
Аргументом метода является необязательный параметр personId (число) — если не передан, то вернется список всех ролей. Метод возвращает Promise с массивом объектов-ролей. Для каждой роли доступны поля:
- role_id — id роли;
- role_name — название роли;
- member_ids — id всех пользователей-участников роли.
Вспомогательные методы
Метод getDaysCount
interface form { getDaysCount( startDate: Date | string, endDate: Date | string, options?: { exclude: "nonWorkingHoliday" | "nonWorking" calendar: "ru" | "kz" | "us" } ): number }
Метод getDaysCount
используется для получения количества дней между датами согласно рабочему календарю. По умолчанию используются настройки организации или пользователя, но при желании можно указать опции через необязательный аргумент options
.
Например, чтобы вычислить нерабочие праздничные дни для поля Даты отпуска (поле типа Срок вида «Дата и период») и заполнить числовое поле Количество дней, используется следующий скрипт:
form.onChange(['Даты отпуска']).setValue('Количество дней', state => { const [due] = state.changes; return form.getDaysCount(due.start_date, due.end_date, { exclude: 'nonWorkingHoliday', calendar: 'ru' }); });
Метод getDateWithTimezoneOffset
interface FormProxy { getDateWithTimezoneOffset(date: string): Date; }
функция getDateWithTimezoneOffset
преобразует переданное значение поля типа Дата в значение, в котором учтено смещение по часовому поясу.