META

Смарт-формы

Мотивация

Довольно часто нужно нарисовать типовую форму для добавления или редактирования строки таблицы Логирование через ObjectLogService будет выполнено автоматически, а значит и Шина сможет принять событие, если будет нужно.

Автоматическая генерация формы

Форма генерируется по внутреннним правилам, но подвигать контролы можно через их позициии в списке fields в yaml файле или атрибут order, но даже это не гарантирует, что они выстроятся в нужном вам порядке - мета сортирует блоками в соответствии со внутренними правилами.

Тип me-input так же пытается подобраться автоматически, исходя из заполненности полей:

  • name
  • db_type
  • data_type
  • semantic_role

Ширина me-input для поля высчитывается автоматически исходя из разных параметров, но может быть задана через атрибут поля lego_elem.span. В целом иногда есть задача вывести поле как textarea, для этого используется атрибут lego_elem. По сути там пишется все то, что можно писать в обычных ручных lego формах, но не обязательно описывать вообще все - впишите только то, что нужно переопределить по отношению к автосгенерированным свойствам.

Например:

...
- alias: name
  db_type: text
  is_title: true
  display_name: Название клиента
  name: name
  semantic_role: DIMENSION
  order: 100 <---------- сортировка, заданная вручную и не через само положение относительно других полей entity
  data_type: TEXT
  is_required: true
  lego_elem:  <---- Все то, что работает в lego формах (НЕ ОЯЗАТЕЛЬНО полное описание всего elem). Перетирается важными сгенерированными параметрами, например id, label, entityId, required и пр 
    name: me-input
    span: 4
    attrs:
      type: textarea
...

Настройка insert_query

Иногда бывает, что надо вызвать хранимую процедуру для вставки объекта. Это можно сделать через настройку параметра insert_query в yaml, нужной entity. Данные формы придут как обычно в :env.sp.obj.*

ВАЖНО! Вы будете обязаны вернуть одну строку с одним полем с названием id.

default_value

Значение default_value шаблонизируется через freemarker так же как и любой шаблон и скрипт в мете. Из этого следует, что вы можете рассчитывать динамические значения, например дату по умолчанию. Вот пример одного поля:

- name: "pay_date"
  db_type: "date"
  default_value: "${ref.now.plusMonths(1)?date}"
  display_name: "Дата оплаты"
  i18n: true

Пример и документация использования Java API для работы с датами: https://samples-demo.devision.io/page?p=3750&a=35

Параметры add_expression и set_expression

Иногда при insert или update записи надо иметь высчитываемые поля, но часто они зависят от контекста, например userId. В примере нише рассматривается простейший случай, когда нам надо просто взять и вставить id пользователя (используется prepared statement), но тут можно написать и более сложный sql expression

- name: author_user_id
  db_type: bigint
  display_name: Добавлено
  i18n: true
  foreign_entity_id: 2654
  is_readonly: true
  add_expression: :env.userId
- name: last_user_id
  db_type: bigint
  display_name: Изменено
  i18n: true
  foreign_entity_id: 2654
  is_readonly: true
  add_expression: :env.userId
  set_expression: :env.userId

Связанный enum

Enum-ы хранятся в таблице meta.enum и являются простыми справочниками, для которых не хочется заводить отдельные таблицы.

В смарт-формах можно очень легко автоматически получить контрол me-select для таких полей просто сделав ссылку на нужный вам справочник.

...
- db_type: text
  name: state
  foreign_enum_kind: 'billing_state' <-------- эта настройка управляет ссылкой на enum 
  lego_elem:
    span: 4
...

Мета внутри себя для такого поля сделает запрос:

SELECT * FROM meta.enum_options_view WHERE kind=:kind ORDER BY name

В плейсхолдер :kind будет подставлено значение из foreign_enum_kind, в этом конкретном примере - billing_state.

Практический пример

Пример настройки entity

id: '190'
name: Клиент
db_alias: adplatform
schema: public
table: client
alias: client
kind: DICT
metaql_where: '#table.id in (select client_id from get_user_clients(${env.userId?number}))'
input_options_query: >
  select
    client.id,
    client.name,
    categories.name as description
  from client
    left join company on company_id = company.id
    left join client_category as categories on categories.id = client.category_id
  where client.is_enabled
  AND client.company_id=:env.companyId::bigint
  AND client.id in (select client_id from get_user_clients(:env.userId::bigint) )
  ORDER BY 2  

insert_query: >
  SELECT client_id as id FROM ext_garpun_main."api_addClient"(
    :env.userId::bigint, :env.companyId::bigint,
    :env.sp.obj.name::text, 11
  );  
search_order: 1
object_list_base_page_id: '2176'
acl: 
  view:   /* Не забывайте про ACL. У вас он может быть другой  */
    roles:
    - meta.role.auth
fields:
- alias: id
  db_type: bigint
  is_primary: true
  name: id
  display_name: ID
  semantic_role: DIMENSION
  data_type: LONG
  is_disabled: true
- alias: name
  db_type: text
  is_title: true
  display_name: Название клиента
  name: name
  semantic_role: DIMENSION
  data_type: TEXT
  is_required: true
- alias: is_enabled
  db_type: bool
  name: is_enabled
  display_name: Активный
  semantic_role: DIMENSION
  data_type: BOOLEAN
- alias: is_archived
  db_type: bool
  name: archive
  display_name: Архивный
  semantic_role: DIMENSION
  data_type: BOOLEAN
- alias: category_id
  db_type: int8
  name: category_id
  data_type: LONG
  foreign_entity_id: 2768
  semantic_role: DIMENSION
  default_value: 11
- alias: creation_time
  db_type: timestamp(0)
  name: creation_time
  display_name: Дата создания
  i18n: true
  data_type: DATETIME
  semantic_role: DIMENSION
  is_disabled: true
- alias: modification_time
  db_type: timestamp(0)
  name: modification_time
  display_name: Дата изменения
  i18n: true
  data_type: DATETIME
  semantic_role: DIMENSION
  is_disabled: true

Пример 1

Например, мы хотим получить форму редактирования всех полей нужной entity_id. Добавляем страницу в рамках нужной entity. В нашем случае для примера 190. Url меты должен быть примерно таким, когда вы работаете со страницей - /card?e=190&o={ТУТ_ID_ОБЪЕКТА} Это все, что надо на странице:

<script type="meta/js" elem="me-smart-form" id="smart_form">
function main(SmartFormService, pvm, vm, env, originalEnv) {
  SmartFormService.handle(env, originalEnv, vm, pvm);
}
</script>

Мета автоматически нарисует текстовые поля для ввода текстовых значений, чекбоксы для boolean, нарисует выпадашки для полей, с указанным foreign_entity_id

Пример 2

Мы хотели получить редактор записи клиента, но так, чтобы при добавлении было только поле name, а при редактировании к нему добавлялся бы выбор category_id

Это все, что надо на странице:

<script type="meta/js" elem="me-smart-form" id="smart_form">
function main(SmartFormService, pvm, vm, env, originalEnv) {
  var fieldNames = ['name'];
  if (env.hasObjectId) {
    fieldNames.push('category_id');
  }
  SmartFormService.handle(env, originalEnv, vm, pvm, {
    fieldNames: fieldNames
  });
}
</script>

Что далее?

Не забудьте про обработчик событий сущности check_access для того, чтобы не делать проверку доступа к объекту на каждой странице сущности.

Новый прием в смартформах ☝🏽👾👾👾

раньше в ямлах в запросе input_options_query мы могли использовать ТОЛЬКО параметры, которые ВСЕГДА передаются в этот запрос или которые доступны из окружения (например :env.companyId).

📍теперь можно использовать параметры, которые не всегда передаются в этот запрос при вызове сущности Сотрудник (например: COALESCE(NULLIF(’${env.sp.date}’, ‘’)::date, ‘1970-01-01’::date) ) т.е. параметр не всегда передается в запрос. в моем примере он передается только из формы (:env.entityId = ’employee_distribution_time’), во всех других обращениях к этой сущности ничего дополнительно не передается.

  • ранее не работала передача параметра через шаблонизацию, при выкатке последнего мастера эту возможность добавили.
  • параметр необходимо указывать в кавычках, чтобы он был текстом, тогда, если параметр не придет в запрос, то в запросе окажется просто пустая строка и запрос не упадет.
  • если требуется приведение типов, то пустую строку нужно обработать через COALESCE