Декларативное описание набора строк rowset - XML описание, по которому строются экземпляры наборов строк, привязанные к экранной форме. Описание набора строк – это описание поддерживаемых им запросов и список полей.
При создании набора строк на клиенте у сервера запрашивается его описание. Декларативное описание набора строк rowset запрашивается у сервера 2 раза
Ориентировочный вид декларативного описания таков:
<rowset
(name|rowset)="<имя набора строк>"
datasource="<имя источника данных>"
readonly="0|1"
js_readonly="<JavaScript выражение>"
<!---
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"
timeout="<кол-во секунд между перечитками, для method=timeout>"
url="<url соединения, для mode=polling или socket>"
ignoreerror="0|1"
>
<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"
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"
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="<имя возвращаемого параметра>" value=""/>
...
<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();