META

Типы и атрибуты скриптов

Мотивация

Скрипты это блоки получения или изменения данных, имеют атрибуты:

  • type - определяет тип получения данных: sql, js
  • id - имя данных для меты, используется для последующих обращений к данным. Грубо говоря имя переменной
  • elem-attrs - доп. атрибуты элемента конкретного типа

Атрибуты elem-attrs

Конфигурируют элемент, указанный в атрибуте elem. Для каждого elem они могут быть разными - ищите нужную документацию в структуре всех классов.

Чаще всего вы будете использовать атрибуты, указывая их в виде json-обекта как в этом примере:

<script type="meta/sql" db-alias="adplatform" id="issues" order="100500" entity-id="2701"
		elem-attrs='{"addObjectButton":{ "mode":"sidenav"}}'>
...

(beta) Для атрибутов типа строка и число вы можете использовать inline стиль написания, это удобно для более простой шаблонизации или боее ясного написания сложных объектов

<script type="meta/sql" order="400" id="res" entity-id="2830" db-alias="meta" entity-id="ex_system" 
  elem="me-card-list"
  elem-attrs.entityCardLink.mode='sidenav'
  elem-attrs.entityCardLink.drawTopMenu='false'
  elem-attrs.grid.cardMaxWidth="350px"
  elem-attrs.grid.sizeNormal="20"
  elem-attrs.grid.sizeMd="25"
  elem-attrs.grid.sizeSm="33"
  elem-attrs.grid.sizeXs="100"
  elem-attrs.helpTemplate.cssStyle.background='#3689ff'
  elem-attrs.helpTemplate.cssStyle.color='#fff'
...

Отображение элементов в сетке

UI сетка состоит из 12 столбцов, ячейки сетки не используют flex, идут слева направо и естественным образом переносятся на новую строку. Если нужно сделать новую строку используйте элемент с именем newrow:

<elem name="newrow"></elem>

Или если вы описываете LEGO на json, то это выглядит так (полный пример):

{
  "name": "newrow"
}

Атрибут order

Атрибут Не влияет на порядов исполенения скриптов на backend, а только определяет спорядов в сетке. Чем больше значение, тем правее и ниже будет элемент.

Атрибут span

Определяет ширину элемента в сетке. Например если будет span=“12” (значение по-умолчанию) то элемент будет занимать всю ширину страницы, span=“6” - только половину.

Атрибут offset

Работае как и span по ширине в сетке, но отвечает за левый отступ. Например, если у нас span=“4” и offset=“4”, то наш элемент будет занимать 1/3 ширины страницы и на 1/3 будет сдвинут слева, т.е. будет находится примерно по середине.

Атрибут align

Центрирует контент элемента. Может быть: left (по умолчанию), right или center.

Ключевое слово internal

Используется для того, чтобы получить данные на серверной стороне и не отправлять их на клиент. Это ускоряет страницу и позволяет 100% защитить программиста от случайно передачи секретной информации на клиент.

<script type="meta/sql" id="my_data" internal>
  SELECT 1
</script>

Ключевое слово async

Является ли скрипт асинхронным, по умолчанию равно false. Если true, то при загрузке страницы скрипт не будет выполнен. Будет считаться, что эти данные будут загружаться браузером асинхронно. При этом это может использоваться как для me-input, так и для просто refPvmData. Это очень удобно для медленных графиков или того, что можно догрузить после всего.

Условия do-if

Используется для условной фильтрации скриптов и элементов на бекенде. Скрипты не будет исполняться и будет предотвращать отрисовку связанных элементов. Элементы, при установки do-if на них не будут приходить на фронтенд

Пример: https://samples-demo.devision.io/page?p=4551&a=35

<script type="meta/sql" db-alias="meta" id="all_activity" do-if="env.sp.activity_mode=='all'">
select now()
</script>

Атрибут exportable (по умолчанию true)

Управляет возможностью выгружать таблицу

Атрибут analyzable (по умолчанию true)

Управляет возможностью делать анализ данных на таблице

SQL управляемый скрипт (meta/sql)

Основной способ получения данных, рекомендуется вибирать именно его, а не meta/js в пробладающем кол-во вслучаев. Исполняется на указанной в db-alias базе данных Выполняет SQL с синтактисом указанной БД без магических модификаций

Существуют макросы для упрощения жизни разработчика:

${pager} - для вставки пагинатора

${sort} - для вставки сортировки

<script type="meta/sql" db-alias="meta_samples" id="tableWithFooterWithoutPagerTotal" 
    label="Таблица с футером, но без расширенного пагинатора" span="4" order="71">
WITH empls AS (
SELECT (generate_series || employee."employeeId"::text)::int as seq,
employee.*
FROM "public"."employee", generate_series(1, 50)
)
SELECT
  empls.seq, empls."employeeId", empls.name, empls.salary,
  json_build_object(
    'footerRows', json_build_array(
      json_build_object('name', 'Ср. зарплата', 'salary', (avg(empls.salary) over())),
      json_build_object('name', 'Итого', 'salary', (sum(empls.salary) over()))
    )
  ) as table_props_field
FROM empls
order by ${sort}, 1
${pager}
</script>

Лимит строк (атрибут max-rows)

По-умолчанию 50 000 строк данных будет нормально возвращаться для скрипта или любого другого запроса в БД. Если строк придет больше, то скрипт вернет ошибку и не вернет результатов. Если вам не хватает значения 50к - вы можете увеличить его атрибутом скрипта

<script ... max-rows="999999">
...

Чтение и Обновление данных (атрибут no-result)

По-умолчанию script выполняет функция query() в сервисе запросов к БД. Функция query ожидает, что запрос вернет результат, поэтому c SELECT все будет хорошо, а для INSERT, UPDATE, DELETE и пр нужно делать RETURNING если это Postgres, либо ставить no-result на скрипте. Это будет вызывать функцию update(), которая ждет, что ничего не будет возвращено и будет ругаться, если что-то вернется

Пример: https://samples-demo.devision.io/page?p=4105&a=35

<script type="meta/sql" db-alias="meta_samples" id="upd"
  no-result>
UPDATE counters SET inc = inc + 1 WHERE name='md_source_update'
</script>

JavaScript управляемый скрипт (meta/js)

Рекомендуется обрабатывать данные внутри БД, а не затаскивать в код приложения, чтобы, например почистить данные или не дай бог просуммировать. PostgreSQL очень хорошо справляется почти со всем задачами по обработке данных.

Обрабатывается через Nashorn поэтому работает не всегда так же как обычный JavaScript

В скриптах выглядит следующим образом:

<script type="meta/js" db-alias="meta_samples" id="tbl_with_js_footer" >
function main() {
  return DataResult.newBuilder()
    .field('id', 'id', 'TEXT')
    .field('name', 'name', 'TEXT')
    .field('value', 'value', 'LONG')
   
    .headerRow({
      name: {
        value: '100%',
        description: "% - это ..."   
      },
      value: {
        value: 2230,
        description: "Текст описания, возможно достаточно длинный"
      }
    })
    .headerRow({value: {
      value: '110%'
    }})
   
    .row({id: 1, name: "A", value: 10})
    .row({id: 2, name: "B", value: 20})
   
    .footerRow({name: "Первая строка", value: 2230})
    .footerRow({name: "Вторая строка", value: 330})
    .footerRow({name: "N строка", value: 2000})
    .build();
}
</script>

Пример со страницы http://samples-demo.devision.io/page?a=35&p=3497

Функции

  • json.to(Object) - объект в json
  • json.from(String) - json в объект
  • MetaUtils.convert.toDouble(String) - переводит строку в число с плавающей точкой
  • MetaUtils.convert.toInt(String) - переводит строку в число int
  • MetaUtils.convert.toLong(String) - переводит строку в число bigint
  • MetaUtils.meinput.filecontent.readBase64(String) - переводит html base64 строку в строку, пригодную для чтения функиями работы с base64

Типичный код после UPDATE/INSERT в карточке объекта. Кидает попап, переводит состояние страницы в default с очисткой параметров env.sp А так же логирует действие с объектом.

<script type="meta/js" elem="hidden" states="save">
function main(pvm, vm, env, ObjectLogService) {
  var  objectId = MetaUtils.convert.toLong(env.object_id);

  pvm.popup = {level: "success", message: "Объект сохранен"};
  pvm.setEnvSp({});  
  pvm.changeState('default');
  ObjectLogService.logValue(env.entityId, objectId, 'SET', env.sp.obj);
}
</script>

Частичное изменение env sp с backend (patchEnvSp)

Пример: https://samples-demo.devision.io/page?p=5196&a=35

Например, вы хотите реализовать такую логику: “Пользователь на форме ввел ИНН в поле, бекенд вернул часть value данных для формы и Мета все заполнила. Поля после этого можно изменять, но если ИНН изменят опять - данные перезаполняются.”

Делается через механизм серверной валидации, так как скорее всего мы заходите проверить ввод данных, перед заполнением других полей. Механизм refPvmValidator позволяет изменять env.sp, если вы вызовите pvm.patchEnvSp. Если в refPvmValidator вам нужны данные из БД, используйте additionalPartialLoad, например так:

refPvmValidator: {id: "my_validator", sp: {}, additionalPartialLoad: ["my_external_sql_data", "my_data_loader"]},

role=“alert”> Обратите внимание, что patchEnvSp перечает частичные данные и мета делает deep merge и перезаписывает только те данные, которые вы передали.

Пример 1:

  • Было: {“a”: 1, “b”: {“in”: 42}}
  • Передали: {“a”: 2}
  • Будет: {“a”: 2, “b”: {“in”: 42}}

Пример 2:

  • Было: {“a”: 1, “b”: {“in”: 42}}
  • Передали: {“b”: {“foo”: 777}}
  • Будет: {“a”: 1, “b”: {“in”: 42, “foo”: 777}}

Пример 3:

  • Было: {“a”: 1, “b”: {“in”: 42}}
  • Передали: {“b”: {}}
  • Будет: {“a”: 1, “b”: {“in”: 42}}

Для полной перезаписи env.sp используйте pvm.setEnvSp()

<script id="my_validator" type="meta/sql" db-alias="meta_samples" depends="name">
SELECT 'unique_client' as id, 'Клиент не может присутствовать в списке больше одного раза: ' as name
LIMIT 0
</script>

<script id="my_external_sql_data" type="meta/sql" db-alias="meta_samples" depends="name">
SELECT NOW() as my_time
</script>

<script type="meta/js" id="my_data_loader" depends="name">
  function main(pvm, env) {
  if (pvm.data.my_validator.rows.length === 0) {
    pvm.patchEnvSp({obj: {
    name_gen: env.sp.obj.name + "11111   " + pvm.data.my_external_sql_data.rows[0].my_time,
    other_form_data: env.sp.obj.name + "222222",
    }});
  }
  }
</script>

<elem states="default">
  <tpl>
  <form name="editGroupForm" ng-submit="changeState('save', {obj:env.sp.obj})">
    <me-lego elems="editGroup.legoForm"  output="env.sp.obj" ></me-lego>
  </form>
  </tpl>
  <script type="meta/js" id="editGroup" states="default">
  function main(env, log, vm, pvm) {
    vm.legoForm = [
    {
      id: "name",
      label: "Имя сотрудника",
      help: 'Это имя будет отображаться на списках',
      name: "me-input",
      refPvmValidator: {id: "my_validator", sp: {}, additionalPartialLoad: ["my_external_sql_data", "my_data_loader"]},
      attrs: {
      type: "text",
      placeholder: 'Иван',
      required: true,
      min: 3,
      max: 100
      }
    },
    {
      id: "name_gen",
      label: "Поле с генерируемым контентом",
      name: "me-input",
      attrs: {
      type: "text",
      placeholder: 'Иван',
      required: true,
      min: 0,
      max: 100
      }
    },
    {
      id: "other_form_data",
      label: "Еще какое-то поле",
      name: "me-input",
      attrs: {
      type: "text"
      }
    },
    {
      name: "me-submit",
      attrs: {
      value: 'Сохранить'
      }
    },
    ];  
  }
  </script>
</elem>

<elem states="save">
  <tpl>
  Данные формы:
  <pre>
  
  {{env.sp|json}}
  </pre>
  </tpl>
</elem>

Заполнение Env при первоначальной загрузке страницы

pvm.initEnvSp заполняет env.sp со стороны сервера первый раз при загрузке страницы.

Пример заполнения на константами

<script type="meta/js" id="my_init_script" >
  function main(env, pvm) {
    pvm.initEnvSp(env, {
      "period": {"from":"${ref.now.withDayOfMonth(1)?date}", "to":"${ref.now?date}"}
    });
  }
</script>

Пример заполнения env.sp из запроса

<script type="meta/sql" id="my_init_info" internal>
SELECT ........
</script>
  
<script type="meta/js" id="my_init_script" >
  function main(env, pvm) {
    var init_info = pvm.data.my_init_info.rows[0];
    pvm.initEnvSp(env, {
      "period": {"from":init_info.start_date, "to":init_info.end_date}
    });
  }
</script>