Содержание
Декларативное описание набора строк rowset
Введение
Декларативное описание набора строк rowset - XML описание, по которому строются экземпляры наборов строк, привязанные к экранной форме. Описание набора строк – это описание поддерживаемых им запросов и список полей.
Подробнее
При создании набора строк на клиенте у сервера запрашивается его описание. Декларативное описание набора строк rowset запрашивается у сервера 2 раза
- Первый раз описание запрашивается у источника данных. Возвращаемое им описание не привязано к контексту использования, в нем отсутствуют связи с другими наборами строк.
- Второй раз описание берется из описания экранной формы, в которой набор строк расположен. Это описание имеет более высокий приоритет, оно подстраивает работу набора строк к контексту использования внутри экранной формы. Тут задается взаимодействие с другими наборами строк, расположенными в этой экранной форме.
Ориентировочный вид декларативного описания таков:
<rowset name|rowset="имя набора строк" datasource="имя источника данных" readonly="0|1" js_readonly="JavaScript выражение" /* устарело, заменено на autoexec autorefresh="автоперечитка в секундах" autorefreshrow="автоперечитка в секундах" ignoreerror="0|1" */ js_refresh="JavaScript выражение, изменение которого автоматически вызывает перечитку" parent.row.type="допустимый row.type узла родительского набора строк" parent.sync.row.type="допустимые дочерние типы узлов через ;" parent.sync.field.icon="имя поля, задающего иконку синхронизируемого узла" parent.sync.field.empty="имя поля, задающего признак row.empty синхронизируемого узла" parent.sync.field.final="имя поля, задающего признак row.final синхронизируемого узла" anketa="0|1" filter="0|1" filter.autorefresh="1|0" paginator.count="0|кол-во строк в странице" row.focus.id="id для первого позиционирования" row.focus.path="path для первого позиционирования (через ;)" evt_onnavigate="имя запроса" js_onnavigate="JavaScript выражение" mark.rowset="имя источника данных для хранения пометок" mark.rowset.fieldmark="имя поля для id пометки" mark.rowset.fieldtype="имя поля для row.type" mark.all="0|1 (пометить все после первого refresh)" > <autoexec enabled="1|0" request="refresh|refreshrow|любой запрос" mode="timeout|polling|socket|event" event="наименование события, вызывающее срабатывание, для method=event" timeout="кол-во секунд между перечитками, для method=timeout" url="url соединения, для mode=polling или socket" ignoreerror="0|1" pingtimeout="периодичность отправки ping запросов для проверки соединения socket, в секундах" > <param name="имя параметра" value="значение параметра"/> ... <param name="имя параметра" value="значение параметра"/> </autoexec> <section row.type="тип узла" field.name="имя поля" field.description="имя поля" field.form="имя поля" field.params="имя поля" default.icon="иконка по умолчанию" default.final="0|1" > <requests> <params> <param name|param="имя параметра" value="значение" js_value="JavaScript выражение" type="string|memo|date|num|check|ref" enabled="1|0" js_enabled="JavaScript выражение" skipempty|notempty="0|1" /> ... <param/> </params> <request name|request="имя запроса" mode="подрежим запроса" enabled="0|1" js_enabled="JavaScript выражение" js_caption="JavaScript выражение" js_icon="JavaScript выражение" caption="текст" icon="иконка" timeout="время ожидания ответа" sync="0|1" > <params> <param name|param="имя параметра" value="значение" js_value="JavaScript выражение" type="string|memo|date|num|check|ref" enabled="1|0" js_enabled="JavaScript выражение" skipempty|notempty="0|1" /> ... <param/> </params> </request> ... <request/> </requests> <fields> <field name|field="имя поля" enabled="0|1" type="string|password|memo|date|datetime|num|check|ref|list|radio|icons|multilist|multicheck|multitree|html" list="code=value;item1;item2;item3;...;itemN" listfield="имя поля со списком допустимых значений id=value;id=value" listrowset="имя источника данных, со списком допустимых значений" basetype="string|num" triplex="0|1" dlgstyle="auto|list|check" dlgcheckmax="для режима dlgstyle=auto максимальное кол-во строк, отображаемых как check, по умолчанию 25" size="small|medium|large" caption="заголовок" visible="0|1" js_visible="JavaScript выражение" readonly="0|1" js_readonly="JavaScript выражение" notnull="0|1" maxlength="максимальное кол-во символов" width="ширина при отображении: px, pt или %" dlgwidth="ширина диалога при отображении: px, pt или %" len="длина для отображения, в символах" default="значение по умолчанию" rows="высота в строках для многострочных редакторов" maxrows="максимально допустимая высота в строках для многострочных редакторов" dec="кол-во знаков после запятой" hidezero="0|1" numsep="0|1" stretch="0|1" refid="имя поля справочника" refname="имя поля в источнике данных справочника - для переноса" reftext="имя поля в источнике данных справочника - для отображения в списке" save="0|1" enter="0|1" - для memo Enter как перевод строки (иначе переход на следующее поле) nowrap="0|1" - отображать без переноса (в grid одной строкой) onaction="текст запроса" js_onaction="JavaScript выражение" > <ref datasource="имя источника" row.type="для деревьев, подходящие типы узлов через ;" form="имя модальной формы справочника" form.result.mode="result_mode" form.result.value="result_value" > <param name|param="имя параметра" js_value="JavaScript выражение"/> ... <param/> <result name="имя элемента #data" value="имя возвращаемого элемента #data в закрывающейся модальной форме справочника"/> ... <result/> </ref> <change> <clear name|field="имя очищаемого поля"/> ... <clear/> <param name|param="имя параметра" js_value="JavaScript выражение"/> ... <param/> </change> </field> ... <field/> </fields> <marked>id1,id2,id3,...,idN</marked> </section> <childs> Список дочерних наборов строк </childs> </rowset>
Атрибуты набора строк
Атрибуты набора строк имеют следующий смысл:
- readonly - если параметр равен 1 то набор строк нельзя править. Часто формируется внутри скрипта источника данных, исходя из информации о правах текущего пользователя.
- filter - если 1, то набор строк используется как фильтр, задающий условия для дочерних наборов строк. Его особенности:
- Никак напрямую не взаимодействует с сервером (на сервере ему не соответствует никакого источника данных)
- Полностью описывается в экранной форме
- Всегда корневой: не может иметь родительского набора строк
- Содержит одну и только одну строку, запрещена вставка и удаление строк
- Значения полей проинициалезированны параметрами default из описаний полей
- На каждое изменение любого поля автоматически производится перечитка дочерних наборов строк, такое поведение можно отменить параметром filter.autorefresh=0
Секция <scripts>
Секция <scripts> предназначена для описания кода JavaScript функций, нужных для вычисления динамических выражений. Обычно бывает ненужна.
Секция <requests>
Секция <requests> предназначена для описания поддерживаемых источником данных запросов. Там должны быть перечислены все допустимые для источника данных запросы. Единственное исключение - запрос request name=«change» может быть описан внутри поля, изменение которого должно инициализировать выполнение этого запроса.
В описании запроса используются следующие атрибуты:
- name|request - имя запроса
- enabled – доступность выполнения запроса, по умолчанию доступно
- js_ensbled – выражение JavaScript динамически определяющее доступность выполнения запроса для текущего контекста.
- icon - иконка для обозначения операции
Передаваемые запросу параметры описываются в секциях <params> запроса.
Особым образом обрабатываются параметры секции <params> набора строк. Описанные там параметры передаются всем запросам, и их описания имеют более высокий приоритет, чем описание параметров внутри запроса. Эту секцию добно использовать для подстройки поведения набора строк под контекст использования в экранной форме.
Атрибуты узла <param> имеют следующий смысл:
- name|param – имя параметра
- value - значение параметра
- js_value – выражениена JavaScript, позволяющее динамически вычислить значение параметра по контексту.
- type - тип для приведения
Для упрощения описания источника данных, при объявлении параметров действуют следующие умолчания:
- Значение id=«id текущей строки» всегда автоматически вычисляется и передается запросу, его не надо специально объявлять.
- Для запроса save значения отредактированных и еще пока не сохраненных полей, текущей строки источника данных, автоматически вычисляются и передаются в запрос, их не надо специально объявлять.
- Для передачи запросу значения поля текущей строки источника данных достаточно в атрибуте name указать имя поля, а js_value не указывать.
Запрос change описывается внутри поля, при изменении которого вызывается. Его не нужно описывать в списке операций. Ему всегда передается текущее значение поля, в котором он объявлен
Описание полей источника данных
Имя поля задается значением атрибута field
Рассмотрим особенности реализации типов данных для полей:
- string – строка, просмотр и редактирование однострочным редактором
- memo – многострочная строка
- num – число в LSS формате
- date – дата в LSS формате
- datetime – дата и время в LSS формате. Если параметр dec=0 или не задан, то с точностью до минут. Если dec=1, то с точностью до секунд. Если dec=2, то с точностью до милисекунд.
- check – логическое значение в LSS формате
- refname – поле выбирается из справочника, для описания необходимо задать refid – имя поля ссылки на строку справочника и refname – имя поля внутри справочника.
- refid – поле ссылки на строку справочника. Обязательно должен присутствовать дочерний узел <ref> с описанием обращения к источнику данных (запрос request=«select»), возвращающего строки справочника.
- multilist, multicheck – поле содержит значения ссылок на строки справочника, через запятую.
- допустимые значения могут быть заданы параметром list в виде: id1=value1;id2=value2;id3=value3;…;idN=valueN
- допустимые значения могут быть заданы параметром listfield, содержащем имя поля, в котором хранятся значения в виде: id1=value1;id2=value2;id3=value3;…;idN=valueN
- допустимые значения могут быть заданы посредством дочернего узла <ref> с описанием обращения к источнику данных (запрос request=«refresh»), возвращающего строки справочника. Используются параметры: refname, reftext
- допустимые значения могут быть заданы параметром listrowset, содержащим имя источника данных, содержащего начитанные значения. Используются параметры: refname, reftext.
Атрибут visible задает значение по умолчанию для видимости поля во всех строках. Если параметр не задан, то считается 1 (поле видимо). Если видимость поля зависит от контекста, то в атрибутах конкретного поля внутри конкретной строки можно задать параметр visible, который перешибет в этой строке значение, заданное для всей колонки.
Атрибут readonly задает доступность колонки для редактирования по умолчанию. Если возможность правки поля зависит от контекста, то в атрибутах конкретного поля внутри конкретной строки можно задать параметр readonly, который перешибет в этой строке значение, заданное для всей колонки.
Атрибут notnull задает требование непустого значения для поля.
Атрибут save=«1» - принудительное сохранение строки при изменении поля
Особенности поля memo
- rows - высота экранного элемента в строках, по умолчанию 3
- maxrows - ???
- enter = 0|1 - обработка клавиши Enter как перенос строки, иначе переход на следующее поле. Shift+Enter - всегда отрабатывает как перенос строки
Особенности поля num
- len - общее кол-во знаков
- dec - кол-во знаков после запятой
- hidezero = 0|1 - нулевые значения отображать как пробел (пусто)
- numsep = 0|1 - отображать с разделителем между тысячами (тройками цифр) для наглядности
Особенности поля datetime
- dec - точность хранения времени
- 0: часы:минуты
- 1: часы:минуты:секунды
- 2: часы:минуты:секунды.мс
Раздел autoexec
Этот раздел позволяет периодически вызывать указанный в разделе запрос
<autoexec enabled="1|0" request="имя запроса" mode="timeout|polling|socket" timeout="кол-во секунд" url="url соединения, для mode=polling или socket" ignoreerror="0|1" > <param name="имя параметра" value="значение параметра"/> ... <param name="имя параметра" value="значение параметра"/> </autoexec>
Описание параметров
- enabled=«1|0» - по умолчанию включен, позволяет заблокировать autoexec
- request=«имя запроса» - выполняемый запрос, обычно refresh или refreshrow, но можно любой другой, например объявленный на уровне формы
- ignoreerror=«0|1» - по умолчанию выключен, позволяет игнорировать ошибки сервера, возникающие при выполнении запроса
- mode - способ управления вызовом запроса
mode=timeout
В этом режиме запрос вызывается периодически, через каждые timeout секунд
<autoexec enabled="1|0" request="refresh|refreshrow|любой запрос" mode="timeout" timeout="кол-во секунд между перечитками" ignoreerror="0|1" > </autoexec>
mode=polling
В этом режиме запрос вызывается по указанию сервера, longpolling. Для реализации такого способа достаточно Apache + PHP, оповещение работает по http
Клиент создает и держит с сервером продолжительное, заданное параметром timeout (30-300 секунд) http соединение. Сервер периодически, например раз в секунду, проверяет наличие интересующего клиента события. При возникновении события, возвращает клиенту строку 1 и завершается. Если за заданное timeout время событие не возникло, то серверное соединение завершается, ничего не сообщая клиенту. А клиент повторно создает новое соединение и ждет ответа.
<autoexec enabled="1|0" request="имя запроса" mode="polling" timeout="время ожидания polling запроса" url="url polling соединения" ignoreerror="0|1" > <param name="имя параметра" value="значение параметра"/> ... <param name="имя параметра" value="значение параметра"/> </autoexec>
- url - http адрес polling соединения
- timeout - время ожидания для polling соединения
- параметры param - добавляются в url polling запроса
Пример реализации polling сервиса на PHP
<?php class ServiceAutoExecPolling extends ServiceController{ public function getParams() { $result=array(); $result['format']='text'; $result['timeout']=$_REQUEST['timeout']??30; return $result; } /** Формирование результата запроса * * @param array $params параметры * @return string результат */ public function getResult($params) { $result=''; $timeout=$params['timeout']; //предельное время выполнения скрипта ini_set('max_execution_time', $timeout+5); for($i=0; $i<$timeout; $i++) { //проверка на наличие соединения, прекращение выполнения скрипта при разрыве echo ''; ob_flush(); flush(); if ($this->test()) { $result=1; break; } sleep(1); } return $result; } /** Проверка возникновения события * * @return boolean наличие интересующего клиента события */ protected function test() { return false; } } return new ServiceAutoExecPolling(); ?>
Некоторые важные особенности реализации для PHP:
- На входе в сервис автоматически приходит параметр timeout - время выполнения в секундах. Под него сервис должен подстроиться
- Необходимо предотвратить остановку работы PHP скрипта Apache по max_execution_time, для этого max_execution_time должно быть больше, чем timeout
- Необходимо останавливать PHP скрипт при разрыве соединения с клиентом. Для этого надо тестировать наличие соединения, пытаясь отправить клиенту сообщения. Пустое сообщение достаточно для тестирования соединения.
- При возникновении события надо послать клиенту 1 и остановить выполнение PHP скрипта
- При превышении времени необходимо остановить работу PHP скрипта, ничего не отправляя клиенту
mode=socket
В этом режиме запрос вызывается по указанию сервера, через WebSocket. Для реализации такого способа необходим сервер, поддерживающий socket соединение, технологии PHP+Apache недостаточно. Например, на PHP такой сервер можно написать на Ratchet, но это будет отдельный сервис, работающий независимо от Apache.
<autoexec enabled="1|0" request="имя запроса" mode="socket" url="url socket соединения" pingtimeout="периодичность отправки ping запросов для проверки соединения, в секундах" ignoreerror="0|1" > <param name="имя параметра" value="значение параметра"/> ... <param name="имя параметра" value="значение параметра"/> </autoexec>
- url - адрес socket соединения
- pingtimeout - если параметр задан, то клиент будет периодически посылать сообщение ping серверу, для проверки соединения
- параметры param - добавляются в url polling запроса
Пример реализации WebSocket сервера на Ratchet
<?php require_once('.lss-init.php'); $path=pathRootConcat( getCfg('path.root.extlib'), 'Ratchet', 'autoload.php' ); require_once($path); use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use Ratchet\Server\IoServer; use React\EventLoop\Factory; class MyServer implements MessageComponentInterface { protected $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { $queryString = $conn->httpRequest->getUri()->getQuery(); parse_str($queryString, $info); $conn->info=$info; $this->clients->attach($conn); echo '- open'."\n"; } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); echo '- close'."\n"; } public function onMessage(ConnectionInterface $from, $msg) { if ($msg=='ping') { $from->send('pong'); echo '- ping - pong'."\n"; } } public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); echo '- error'."\n"; } public function broadcast($msg) { echo '- отправка сообщений всем пользователям'."\n"; foreach ($this->clients as $client) { $client->send($msg); } } } $loop = Factory::create(); $serverComponent = new MyServer(); $loop->addPeriodicTimer(5, function() use ($serverComponent) { $serverComponent->broadcast('1'); }); $webSock = new React\Socket\Server('0.0.0.0:8080', $loop); $server = new Ratchet\Server\IoServer( new HttpServer( new WsServer($serverComponent) ), $webSock, $loop ); echo "\n".'WebSocket запущен'."\n"; $loop->run(); ?>
