How-to: API контроллера пользовательского представления
Пользовательское представление, написанное на JavaScript, взаимодействует с формой через объект контроллера — с его помощью представление задаёт текущий объект группы, изменяет значения свойств, запрашивает подсказки и вызывает действия или скрипты на сервере. Есть три вида пользовательских представлений; контроллер они получают по-разному, но все обращаются к одному и тому же контроллеру формы, методы которого описаны на этой странице.
Свойства и действия адресуются по их интеграционному имени — имени на форме (либо псевдониму / интеграционному имени NEW / DELETE кнопки), тому же имени, что использует внешний JSON/REST API.
Как представление получает контроллер
| представление | объявление | точка входа JavaScript | контроллер формы — это |
|---|---|---|---|
| React-представление | DESIGN c { custom = 'Name'; } | компонент, принимающий props (data, controller) | props.controller |
| CUSTOM группа объектов | OBJECTS g = Cls CUSTOM 'name' | колбэки render / update | controller.form |
| CUSTOM ячейка свойства | PROPERTIES p CUSTOM 'name' | колбэки render / update | controller.form |
React-представление отрисовывает целый пользовательский контейнер, поэтому controller в его props и есть контроллер формы — методы ниже вызываются прямо на нём.
CUSTOM группа объектов и CUSTOM ячейка свойства отрисовываются классическими колбэками render(element, controller) / update(element, controller, ...) — update дополнительно получает list строк группы либо value ячейки. Контроллер, который они получают, — локальный, привязанный к этой одной группе или ячейке: он добавляет нужные этим представлениям помощники — геттеры значения, текущей строки и стилей и diff / clearDiff для группы объектов, событие правки change для ячейки свойства — описанные в Пользовательские компоненты (объекты) и Пользовательские компоненты (свойства). Локальный контроллер выставляет контроллер формы как controller.form, поэтому форменные методы ниже вызываются через него:
controller.form.changeObject('customer', row);
const total = await controller.form.exec('recalc', orderId);
Строки, которые получает классическое представление, несут те же key и objects, что и строки React (см. Идентификация строки), поэтому форменные методы принимают их без изменений. Локальный changeProperty группы объектов также делегирует: для свойства, которое не является её собственной колонкой, вызов передаётся контроллеру формы и разрешается в пределах всей формы, так что представление может изменить свойство, которое не отображает.
Четвёртая точка входа — действие INTERNAL CLIENT: привязанная к нему JavaScript-функция получает контроллер формы аргументом после параметров вызова. Контроллер пользовательского редактора значения (CHANGE) также предоставляет контроллер формы в поле form.
Контроллер формы
Эти методы одинаковы везде, где доступен контроллер формы, — напрямую как props.controller либо как controller.form. Необязательные аргументы — в скобках.
| метод | что делает | возвращает |
|---|---|---|
changeObject(groupSID, object) | задаёт текущий объект группы | — |
changeProperty(property[, object][, value]) | задаёт значение либо выполняет действие — на текущем объекте или на переданной строке | — |
changeProperties(properties, objects, values) | несколько вызовов changeProperty из параллельных массивов | — |
getPropertyValues(property[, object], value[, mode], ok[, fail][, count]) | ограниченный список подсказок с сервера | — (через ok) |
exec(action, ...params) | выполняет именованное действие | Promise |
eval(script, ...params) | выполняет lsf-скрипт с типизированным run | Promise |
evalAction(script, ...params) | выполняет тело действия (параметры $1, $2, …) | Promise |
change(property, ...keyParams, value) | задаёт глобальное свойство | Promise |
Изменяющие методы (changeObject / changeProperty / changeProperties) ничего не возвращают — новое состояние приходит со следующим обновлением формы; вызывающие сервер методы (exec / eval / evalAction / change) возвращают Promise. Когда интеграционное имя свойства не уникально в пределах формы, группу указывают прямо в имени — 'groupSID.property'; указанная в имени группа имеет приоритет. Если передан объект, свойство разрешается по группе самого объекта, поэтому указывать группу нужно только для изменения без объекта (текущего объекта либо формы changeProperty(property, value) с двумя аргументами). Голое, неуточнённое имя, отрисованное более чем в одной группе, использовать нельзя — такой вызов завершается ошибкой, а не молча меняет не ту группу.
Те же две группы различаются ещё по двум признакам — нужна ли проверка доступа и как в них адресуется объект:
| группа | методы | проверка доступа | как передаётся объект |
|---|---|---|---|
| правка формы | changeObject / changeProperty / changeProperties | нет | строка-цель — строкой данных (row) или дескриптором (row.objects); объект-значение (FK) — своим id |
| вызов сервера | exec / eval / evalAction / change | @@api / права администратора / CUSTOMS формы | объект — своим id |
Пользовательское представление обычно читает состояние из props.data и меняет его методами правки формы — в том числе выполняя действие, добавленное на форму, через changeProperty('action'). Методы вызова сервера (exec / eval / evalAction / change) — это запасной путь, нужный только для того, что формой не выражается: произвольного вычисления на сервере, записи в глобальное свойство, создания объекта.
Правка формы идёт обычным каналом правки и не требует проверки доступа; вызовы сервера её проходят (см. Вызов сервера). Редактируемая строка адресуется дескриптором; любой другой объект — значение FK или параметр действия — передаётся своим числовым id (объект lsFusion из JS передать нельзя).
Использование структурного сокращения
Контроллер формы также повторяет форму props.data: каждая группа объектов — член контроллера под её SID группы, каждое свойство, выведенное на этой группе, — член группы под его интеграционным именем, а каждое форменное свойство (без группы) — член контроллера напрямую. Это альтернатива методам со строковой адресацией — группа и свойство записываются как путь, а не упаковываются в строку 'group.property':
| сокращение | эквивалент |
|---|---|
controller.<group>.<property>.change([object,] [value]) | changeProperty('<group>.<property>', [object,] [value]) |
controller.<group>.<property>.getValues(value[, mode], ok[, fail][, count]) | getPropertyValues('<group>.<property>', value[, mode], ok[, fail][, count]) |
controller.<group>.change(object) | changeObject('<group>', object) — задать текущий объект группы |
controller.<property>.change(value) / .getValues(...) | changeProperty('<property>', value) / getPropertyValues('<property>', …) — форменное свойство |
controller.o.note.change('checked'); // changeProperty('o.note', 'checked')
controller.o.sum.change(row, 100); // changeProperty('o.sum', row, 100) — на переданной строке
controller.o.edit.change(row); // выполнить действие 'edit' на переданной строке
controller.o.change(row); // changeObject('o', row) — задать текущий объект группы
controller.o.customer.getValues(text, 'objects', ok, fail);
controller.total.change(500); // форменное свойство: changeProperty('total', 500)
В каждом узле .change(...) меняет этот узел — значение свойства либо текущий объект группы; .change(...) свойства использует определение «значение или строка» из changeProperty, а .getValues(...) — режимы запроса из getPropertyValues, поскольку каждый вызов просто перенаправляется в плоский метод. Сокращение действует в пределах всей формы — каждая группа объектов и каждое выведенное свойство с интеграционным именем; props.data несёт подмножество внутри контейнера самого представления. Имя, которое перекрыло бы существующий член, остаётся этим членом, а не аксессором: SID группы или форменное свойство, совпадающее с методом контроллера (changeProperty, exec, change, …), форменное свойство, совпадающее с SID группы, либо свойство группы с именем change. Адресуйте его строковой формой.
Изменение текущего объекта и значений свойств
changeObject(groupSID, object) задаёт текущий объект группы groupSID. Здесь object — строка данных этой группы либо непосредственный дескриптор objects (см. правила идентификации ниже), но не голый row.key.
changeProperty(property, value) изменяет property для текущего объекта группы. Чтобы адресовать конкретную строку, она передаётся посередине: changeProperty(property, object, value), где object — строка данных либо непосредственный дескриптор. Когда property — действие (или свойство без изменяемого значения), значение опускается: changeProperty('edit') выполняет его на текущем объекте, changeProperty('edit', object) — на переданной строке.
function orderView(props) {
const controller = props.controller;
return (
<div>
<button onClick={() => controller.changeProperty('note', 'checked')}>Отметить</button>
{props.data.o.list.map(row =>
<div key={row.key} onClick={() => controller.changeObject('o', row)}>
{row.number}
</div>)}
</div>
);
}
В форме changeProperty(property, X) с двумя аргументами платформа определяет, значение X или строка: если свойство принимает значение и X разрешается в строку (строка данных либо непосредственный дескриптор), то X читается как строка и вызов выполняется на ней; иначе X — это значение, и изменяется текущий объект.
changeProperties(properties, objects, values) применяет несколько изменений сразу из параллельных массивов — properties[i] изменяется на values[i] для objects[i] (элемент может быть null для текущего объекта). Каждое имя уточняется так же, как в одиночном changeProperty, — именем вида 'groupSID.property' либо группой переданного объекта.
controller.changeProperties(['note', 'qty'], [null, row], ['checked', 5]);
Групповой объект встроенного примитивного класса — например, навигатор DATE — переводится к значению записью значения самого объекта (changeProperty('VALUE', d) с настоящим JS-Date), вызовом changeObject со строкой из props.data.<g>.list (она несёт дескриптор objects) либо, если группа отфильтрована по data-свойству, изменением этого свойства-фильтра. changeProperty пропускает значение-дату через преобразование, ожидающее JS-Date (каст без проверки): аргумент не-Date — строка из date-инпута, timestamp — бросает getFullYear is not a function, поэтому передавайте настоящий Date, например new Date(year, month - 1, day).
changeProperty и changeProperties ведут себя одинаково; формат зависит от того, что задаётся в значение:
| значение | как передаётся |
|---|---|
| примитив | напрямую: число — для числовых типов, строка, JS-Date — для DATE / TIME / DATETIME / ZDATETIME, логическое значение, null — для очистки |
JSON | JS-объект или массив (сериализуется в JSON) |
| объект (значение FK) | id целевого объекта — row.key его строки (для группы с одним объектом это уже его числовой id) либо id-свойство на форме (например, LONG(obj)), но не дескриптор |
Передача дескриптора (otherRow.objects) в качестве значения FK молча выставляет NULL, без ошибки. Дескриптор нужен только для аргумента object (редактируемая строка) и для changeObject; чтобы задать FK, передавайте id целевого объекта.
Если редактируемое свойство помечено на форме модификатором APPLY (правка применяется сразу), changeProperty фиксирует изменение немедленно. Для простой правки из представления — перенос, изменение размера, правка значения по месту — это предпочтительнее отдельного серверного действия; серверное действие (exec) остаётся для того, что правкой свойства не выражается: создания объекта (NEW), многошаговой логики, открытия формы.
// перенос объекта к другому родителю и правка примитива одним вызовом:
// значение FK — id целевого объекта (row.key целевой строки), значение-примитив — напрямую
controller.changeProperties(['parent', 'value'], [item, item], [targetColumn.key, 5]);
Запрос значений
getPropertyValues запрашивает у сервера ограниченный список подсказок для свойства. Результат передаётся в обработчик ok в виде { data: [ { displayString, rawString, objects }, ... ], more }; more равно true, когда список усечён, поэтому это список подсказок, а не полный SELECT DISTINCT.
controller.getPropertyValues(property[, object], value[, mode], ok, fail[, count]);
-
value— вводимая строка запроса для сопоставления. -
object— необязательная строка (строка данных либо непосредственный дескриптор), ограничивающая запрос этой строкой; опускается для текущего объекта. -
mode— одно из:modeрезультат item.objects'objects'(по умолчанию)подходящие OBJECTSсвойства — выбор объектанепосредственный дескриптор objectsэтого объекта'values'различные значения свойства null(используйтеdisplayString/rawString)'change'подсказки времени правки свойства зависит от свойства 'change'отражает то, как свойство редактируется — пользовательский списокINPUT, ограничениеnotNullили пользовательское действие изменения, — а не уже имеющиеся различные значения. Для свойства, которое в этом контексте изменить нельзя (например, только для чтения или вычисляемое без действия изменения), возвращается пустой список, тогда как'values'всё равно вернёт его различные значения. -
ok(result)/fail()— обработчики успеха и ошибки. -
count— увеличивает число запрашиваемых элементов, для постраничной загрузки.
item.objects из результата режима 'objects' можно передать напрямую обратно в changeObject или changeProperty, чтобы действовать с выбранным объектом:
controller.getPropertyValues('customer', text, 'objects',
result => result.data.forEach(item => console.log(item.displayString)),
() => console.log('failed'));
// выбор первой подсказки как покупателя группы
controller.getPropertyValues('customer', text, 'objects', result => {
const item = result.data[0];
if (item) controller.changeObject('c', item.objects);
}, () => {});
Вызов сервера
exec, eval, evalAction и change выполняются на сервере и возвращают Promise. Они проходят ту же авторизационную проверку и преобразуют результат в значение JS так же, как серверные вызовы классического представления, — описание проверки доступа, привязки параметров и таблицу преобразования результата в JS см. в разделе Вызов сервера. Сквозной пример этих вызовов из представления CUSTOM — в статье How-to: Пользовательские компоненты (вызовы сервера).
exec(action, ...params)— выполняет именованное действие; разрешается его значениемRETURN.eval(script, ...params)— выполняет lsf-скрипт, определяющий собственное действиеrun(типизированные параметры).evalAction(script, ...params)— выполняет тело действия, обёрнутое в действиеrun, с параметрами$1,$2, ….change(property, ...keyParams, value)— изменяет глобальное свойство; последний аргумент — значение, предшествующие — ключи.
Параметры передаются как обычные значения JS (число, строка, логическое значение, Date, объект/массив для JSON-параметра). Объект lsFusion передаётся своим числовым id; если параметр действия типизирован классом, платформа сама находит объект этого класса по id — ручной поиск не нужен. Дескриптор строки в параметре объектом не является: для типизированного классом параметра вызов завершается ошибкой, поэтому передавайте id.
const total = await controller.exec('recalc', orderId);
const doubled = await controller.eval('run(INTEGER a) { RETURN a * 2; }', 21); // 42
await controller.change('archived', orderId, true);
Вызов после закрытия формы отклоняется с ошибкой Form is closed — он никогда не зависает, — поэтому await на закрытой форме попадает в ветку catch.
Идентификация строки
Метод, адресующий строку, принимает одно из:
- объект строки данных, полученный представлением (из
propsReact либо из спискаupdateклассического представления); - копию такой строки через spread или
Object.assign— перечислимый дескрипторobjectsкопируется вместе с ней, поэтому копия разрешается в тот же объект; - непосредственный дескриптор
objects—row.objectsлибоitem.objectsиз результатаgetPropertyValuesв режиме'objects'.
Для адресации строки голый row.key не принимается: key — это токен отображения / React-ключа / диффа, а не вход для разрешения. При этом для группы с одним объектом пользовательского класса значение row.key численно равно id этого объекта, поэтому его можно передать как id целевого объекта в серверный вызов или в значение FK — отдельное свойство для собственного id строки создавать не нужно. Имена полей key, isCurrent и objects зарезервированы на строке — свойство или колонка приложения с одним из этих интеграционных имён будет перезаписано.
Если явный аргумент-объект не разрешается ни в строку, ни в непосредственный дескриптор, платформа не подставляет молча текущую строку: changeProperty и getPropertyValues завершаются ошибкой вместо действия над другой строкой, а changeObject ничего не делает.