Задумка PVTables
- В базе данных, создаются таблицы, которые надо редактировать. С помощью MIGX или другим способом. Или используем существующие.
- В таблице gtsAPI «Таблицы АПИ» создаем краткое описание (иструкции) нужной таблицы.
- При вызове сниппета PVTable указываем название таблицы.
- Затем спиппет подгужает js код PVTables и передает в него название таблицы. PVTables делает запрос к gtsAPI и подгужает описание таблицы, по которому строит таблицу и делает запросы на чтение и запись в gtsAPI.
Быстрый старт
Для самой простой таблицы, в «Таблицы АПИ» нужно только прописать имя таблицы (Если имя совпадает с классом MODX таблицы). Завести и выбрать пакет MODX к которому привязана таблица.
В properties прописать действия доступные для таблицы.
Можно использовать
{ "actions":{ "read":{}, "create":{}, "update":{}, "delete":{}, "subtables":{ "table":"tableName", "where":{ "field1":1 } }, "subtabs": { "test": { "DocOrderLink": { "title": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b", "table": "DocOrderLink", "where": { "type_order_id": 1, "order_id": "id" } }, "OrgsContact": { "title": "\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b", "table": "OrgsContact", "where": { "OrgsContactLink.org_id": "loc_org_id" } } } } } }При первом запросе к таблице gtsAPI автоматически сгенерирует описания полей к таблице:
{ "actions": { .... }, "fields": { "id": { "type": "view", "class": "tSkladSmena" }, "date": { "type": "date", "class": "tSkladSmena" }, "number": { "type": "number", "class": "tSkladSmena" }, "hour": { "type": "number", "class": "tSkladSmena" } } }Или удобнее описания сразу сгенерировать кнопкой:
В properties поля можно добавить, удалить и сменить тип. Прописать «label» — название поля и «readonly»:1 — только для чтения. (Потом добавить «filtrer» с кастомным фильтром.)
Преимущества PVTables
1) Можно быстро сделать простую таблицу.
2) Описания таблиц можно, в отличии от getTables, переиспользовать на других страницах сайта или в других таблицах в качестве подтаблиц. Ну и отсюда недостаток ошибка в таблице распространиться везде где она используется.
3) В gtsAPI для таблиц написан стандартный контроллер таблиц, который уже умеет GRUD для произвольных таблиц базы. Нет необходимости для каждой таблицы писать свой контроллер.
4) Можно, в отличии от getTables, переиспользовать описания полей типа autocomplect и select. Что за поля такие ниже в пункте поля PVTables.
5) Часто вместе со стандартным GRUD в таблице еще нужно какое-то кастомное действие. В PVTables можно использовать внешние action — когда PVTables используется в компоненте Vue, сторонний action — когда в gtsAPI действия перенаправляются в сервисный файл компонента, триггеры — до и после стандартный действий можно вызвать функции из сервисного файла компонента, плагины (Думаю внедрить, но еще не определися какие нужны.). Более подробное описание допвозможностей ниже.
6) Для запросов в базу предусмотренны настройки безопасности. В «Таблицы АПИ» можно разрешить доступ только аутенфицированным пользователям, группам пользователей или пользователям у которых есть определенное разрешение MODX. Эти же разрешения можно прописать отдельно для каждого действия в properties:
"actions": { "create": { "authenticated":1, "groups": "ceh_boss,Administrator", "permitions":"list" } }query в формате pdoFetch
Попробовав писать запросы в mysql в формате pdoFetch, я запросы уже по другому и не пишу. gtsAPI по умолчанию формирует запрос в виде:
$default = [ 'class' => $rule['class'], 'select' => [ $rule['class'] => '*', ], 'sortby' => [ "{$rule['class']}.id" => 'DESC', ], 'return' => 'data', 'limit' => 0 ]Если в таблице нужно отобразить данные из нескольких таблиц базы данных, то в properties описания таблицы нужно использовать интсрукцию «query»:
{ "loadModels": "ExcelRaschet", "actions": { ... }, "query": { "class": "sraschet", "where": { "sraschet.last": 1 }, "select": { "sraschet": "sraschet.id,sraschet.family_id,sraschet.manager_id,sraschet.raschet_date,sraschet.loc_org_id,sraschet.object,sraschet.price,sraschet.Profit,sraschet.comment,sraschet.status_id,sraschet.contact_date" }, "sortby": { "sraschet.id": "DESC" } } }Удобство pdoFetch в том, что можно на «лету» менять запрос. Например, нужно 2 почти одинаковых запроса с разными where. Написали 1 массив запроса pdoFetch, сделали запрос, затем в массиве переписываем where и делаем второй запрос.
В gtsAPI инструкция «query» сливается с запросом по умолчанию через array_merge. И чтобы изменить сортировку по умолчанию достаточно написать:
{ "query": { "sortby": { "id": "ASC" } } }Если в «query» есть «select», то функция автогенерации полей делает описания полей по «select».
Типы полей таблиц
Сейчас заведены типы полей text, textarea, boolean, date, autocomplete, select, (нужно еще html, datetime, file.)
autocomplete и select особые поля.
autocomplete позволяет выбрать вставить в таблицу (в status_id например) id записи из другой таблицы базы данных.
Для того чтобы другая таблица данных могла предоставлять autocomplete в ее описании в properties надо вставить инструкцию «autocomplete»:
{ "actions": { ... }, "autocomplete": { "select": [ "id", "name" ], "where": { "name:LIKE": "%query%" }, "tpl": "{$name}", "limit": 0 } }Например, в настройках таблицы gsRaschet в properties задано поле
{ "fields":{ ... "org_id":{ "type":"autocomplete", "table":"Orgs" } } }Тогда, чтоб gtsAPI могло выполнять autocomplete инструкцию
"autocomplete": { "select": [ "id", "name" ], "where": { "name:LIKE": "%query%" }, "tpl": "{$name}", "limit": 0 }Надо вставить в описание таблицы Orgs в properties.
select — какие поля запращивать в mysql. where — в поле автокомплект можно вводить текст и поле будет искать этот текст в базе по инструкции в where. tpl — строка отображаемая в поле в формате fenom (вообще хотелось бы чтоб vue формировал эту строку, но vue, как мне сказали, парсит на этапе компиляции компонента и на этапе работы js в vue уже нет ). limit — если в другой таблице всего 10 строк, то имеет смысл подружать их все сразу, а если там например 2000 контрагентов, то лучше поставить «limit»:20 и не загружать их все сразу.
На основе select, where, limit формируется простой запрос в базу. Если нужно, то запрос можно кастомизировать с помощью инструкции «query». Например выборка пользователей из группы 6:
{ "actions": { "read": [] }, "autocomplete": { "where": { "modUserProfile.fullname:LIKE": "%query%" }, "query": { "class": "modUser", "leftJoin": { "modUserProfile": { "class": "modUserProfile", "on": "modUserProfile.internalKey = modUser.id" }, "modUserGroupMember": { "class": "modUserGroupMember", "on": "modUserGroupMember.member = modUser.id" } }, "where": { "modUserGroupMember.user_group": 6, "modUser.active": 1 }, "select": { "modUser": "modUser.id", "modUserProfile": "modUserProfile.fullname" }, "sortby": { "fullname": "ASC" } }, "tpl": "{$fullname}", "limit": 20 } }Еще в autocomplete можно добавить инструкцию «default_row» — в формате pdoFetch where для определения строки автокомплета по умолчанию. Вернет в ответ сервера «default»:nomer_stroki.
(parent позже. Надо найти где он вообще использовался.)
Поле select выдает (почти) обычный селект. Только описания доступных опций селекта забиваются таблице «Селекты» в gtsAPI для того, чтобы их можно было переиспользовать в разных таблицах.
В «Опции в JSON» опции можно забить либо:
Мужчина,ЖенщинаЛибо:
[ [1,"Прямоуголка"], [2,"Кругляк"] ]Для select нужно прописать имя поля.
Для autocomplete можно пописать имя поля автокомплект.
Тогда функция автогенерации описаний полей автоматически по имени поля поймет, что полю нужно поставить тип autocomplect и пропишет нужную таблицу.
Также нужный тип поля select прописывается из имени поля селекта.
Допвозможности. Сторонний action
Часто вместе со стандартным GRUD в таблице еще нужно какое-то кастомное действие. Например перерасчет остатков на складе:
По задумке можно написать такой action:
{ "loadModels":"skladmake", "actions": { "skladmake/pereraschet_sklad": { "groups": "ceh_boss,Administrator", "head":true, "icon":"pi pi-trash", "class":"p-button-rounded p-button-danger" } } }«head»:true — вывести кнопку в топ таблицы. «row»:true — вывести кнопку в строке таблицы.
gtsAPI уже поддерживает такие действия, а PVTables еще нет. В gtsAPI такие действия перенаправляются в сервисный файл компонента. В смысле вызывается фанкция handleRequest из файла "/core/components/skladmake/model/skladmake.class.php".
Допвозможности. Триггеры
В gtsAPI при каждом запросе подгружеется класс сервисного файла пакета. Например, "/core/components/$package/model/$package.class.php". Если такой файл есть. Это либо пакет к которому привязана таблица, либо имена пакетов можно задать в «loadModels».
Если в $package.class.php заданна функция regTriggers:
public function regTriggers() { return [ 'DocOrderLink'=>[ 'gtsfunction2'=>'triggerDocOrderLink', ], ]; }то тогда когда gtsAPI выполняет какие-то стандартные действия с таблицей класса 'DocOrderLink' gtsAPI вызывает функцию 'triggerDocOrderLink' до и после выполнения этих действий.
(В принципе это почти плагины MODX только мне удобней сразу писать их в коде файла компонента.)
Например, такой триггер:
public function triggerDocOrderLink(&$params) { if($params['type'] == 'after' and $params['method'] == 'read'){ // при запросе к таблице запрещаем чтение всем кроме администратора if($this->modx->user->id != 1) return $this->error("Доступ только администратору"); } if($params['type'] == 'after' and $params['method'] == 'update'){ // при изменение строки таблицы записываем в нее дату изменения $params['object']->date_update = date('Y-m-d'); $params['object']->save(); } return $this->success(''); }Допвозможности. Внешний action
Внешний action используется тогда, когда при нажатии на кнопку нужно показать юзеру какой-то диалог. Тогда пишем компонент vue. Рекомендую сделать копию ExcelRaschet:
npm run copyСтавим пакет PVTables:
npm i pvtablesв src/App.vue что-то типа:
<template> <PVTables table="sraschet" :actions="actions" ref="childComponentRef" /> <Toast/> </template> <script setup> import { PVTables } from 'pvtables/pvtables' import { ref } from 'vue'; import Toast from 'primevue/toast'; import apiCtor from 'pvtables/api' import { useNotifications } from "pvtables/notify"; const childComponentRef = ref() const createDocDialog = ref(false) const updateDocDialog = ref(false) const api = apiCtor('DocOrderLink') const { notify } = useNotifications(); const actions = ref({ DocOrderLink:{ create:{ head:true, icon:"pi pi-plus", class:"p-button-rounded p-button-success", head_click: async (event,table,filters,selectedlineItems) => { ... } }, update:{ row:true, icon:"pi pi-pencil", class:"p-button-rounded p-button-success", click: async (data, columns,table,filters) => { ... } } }, }) </script>Более подробный пример в https://github.com/touol/ExcelRaschet. (Но и там запутано скоро будет :-()
Для записи в таблицы можно использовать:
import apiCtor from 'pvtables/api' const api = apiCtor('DocOrderLink') try { await api.create(DocLink.value) } catch (error) { notify('error', { detail: error.message }); }Для вызова стореннего action:
const api_sraschet = apiCtor('sraschet',600000) try { const resp = await api_sraschet.action('ExcelRaschet/createAccountIn1c',data.value) } catch (error) { notify('error', { detail: error.message }); }В описании таблицы «actions» описывает одновременно и доступные в gtsAPI действия и одновременно описывает какие действия выводить в таблице PVTables. Для того чтобы, действия не показывать, но чтоб они были доступны добавлена инструкция «hide_actions»:
{ "loadModels": "ExcelRaschet", "actions": { "read": [] }, "hide_actions": { "ExcelRaschet\/createAccountIn1c": [] } }В если компонент vue на основе ExcelRaschet, то там делаем npm run build и в modx прописываем сниппет:
{'!mixedVue' | snippet:[ 'app'=>'ExcelRaschet' ]}подтаблицы и подтабы
Ну и остались подтаблицы и подтабы. Я так их называю. В терминологии PrimeVue это expandedRow c таблицей и expandedRow c табами.
Действия вида:
{ "actions":{ "subtables":{ "table":"tableName", "where":{ "field1":1 } }, "subtabs": { "test": { "DocOrderLink": { "title": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b", "table": "DocOrderLink", "where": { "type_order_id": 1, "order_id": "id" } }, "OrgsContact": { "title": "\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b", "table": "OrgsContact", "where": { "OrgsContactLink.org_id": "loc_org_id" } } } } } }Показывают в строке таблицы кнопки такого вида:
При нажатии на них под строкой таблицы показывается, для subtables, другая таблица. Для subtabs набор табов с таблицами:
Инструкция фильтрует в них строки те что связанны с основной таблицей.
"where": { "type_order_id": 1, "order_id": "id" }«type_order_id»: 1 — фильтровать только документы для расчетов.
«order_id»: «id» — id строки основной таблицы. Если кликаем на кнопке в строке с id=41924, то PVTables отфильтрует строки, где order_id — ид расчета равен 41924.
Так же удобно вывести на 1 странице сразу несколько таблиц в табах:
{'!PVTabs' | snippet : [ 'tabs'=>[ 'DocType'=>[ 'title'=>'Типы документов', 'table'=>'DocType', ], 'DocTypeOrder'=>[ 'title'=>'Типы заказов', 'table'=>'DocTypeOrder', ], ] ]}Раскраска строк таблиц
Часто надо выделить строки таблиц каким-то цветом по каким-то условиям. Сейчас для этого предусмотренна 1 инструкция (в properties описания таблицы), которая присваивает строкам таблиц классы css:
"row_setting": { "class": "{switch $status_id}{case 5}canceled{case 4}hit{case 3}outwork{case 1}work{\/switch}" },Инструкция снова использует fenom. Хотелось бы прокинуть из properties в js PVTables функцию, но это вроде небезопасно :-(.
Ну пока все :-)
Комментарии ()