Декларативное описание набора строк 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>
Атрибуты набора строк имеют следующий смысл:
Секция <scripts> предназначена для описания кода JavaScript функций, нужных для вычисления динамических выражений. Обычно бывает ненужна.
Секция <requests> предназначена для описания поддерживаемых источником данных запросов. Там должны быть перечислены все допустимые для источника данных запросы. Единственное исключение - запрос request name=«change» может быть описан внутри поля, изменение которого должно инициализировать выполнение этого запроса.
В описании запроса используются следующие атрибуты:
Передаваемые запросу параметры описываются в секциях <params> запроса.
Особым образом обрабатываются параметры секции <params> набора строк. Описанные там параметры передаются всем запросам, и их описания имеют более высокий приоритет, чем описание параметров внутри запроса. Эту секцию добно использовать для подстройки поведения набора строк под контекст использования в экранной форме.
Атрибуты узла <param> имеют следующий смысл:
Для упрощения описания источника данных, при объявлении параметров действуют следующие умолчания:
Запрос change описывается внутри поля, при изменении которого вызывается. Его не нужно описывать в списке операций. Ему всегда передается текущее значение поля, в котором он объявлен
Имя поля задается значением атрибута field
Рассмотрим особенности реализации типов данных для полей:
Атрибут visible задает значение по умолчанию для видимости поля во всех строках. Если параметр не задан, то считается 1 (поле видимо). Если видимость поля зависит от контекста, то в атрибутах конкретного поля внутри конкретной строки можно задать параметр visible, который перешибет в этой строке значение, заданное для всей колонки.
Атрибут readonly задает доступность колонки для редактирования по умолчанию. Если возможность правки поля зависит от контекста, то в атрибутах конкретного поля внутри конкретной строки можно задать параметр readonly, который перешибет в этой строке значение, заданное для всей колонки.
Атрибут notnull задает требование непустого значения для поля.
Атрибут save=«1» - принудительное сохранение строки при изменении поля
Этот раздел позволяет периодически вызывать указанный в разделе запрос
<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>
Описание параметров
В этом режиме запрос вызывается периодически, через каждые timeout секунд
<autoexec enabled="1|0" request="refresh|refreshrow|любой запрос" mode="timeout" timeout="кол-во секунд между перечитками" ignoreerror="0|1" > </autoexec>
В этом режиме запрос вызывается по указанию сервера, 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>
Пример реализации 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:
В этом режиме запрос вызывается по указанию сервера, через 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>
Пример реализации 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(); ?>