====== Декларативное описание набора строк rowset ======
[[start:|lss]]
===== Введение =====
[[rowset-definition]] - **XML** описание, по которому строются экземпляры наборов строк, привязанные к экранной форме. Описание [[rowset|набора строк]] – это описание поддерживаемых им запросов и список полей.
===== Подробнее =====
При создании [[rowset|набора строк]] на клиенте у сервера запрашивается его описание. [[rowset-definition]] запрашивается у сервера 2 раза
* Первый раз описание запрашивается у [[Datasource|источника данных]]. Возвращаемое им описание не привязано к контексту использования, в нем отсутствуют связи с другими [[rowset|наборами строк]].
* Второй раз описание берется из описания экранной формы, в которой [[rowset|набор строк]] расположен. Это описание имеет более высокий приоритет, оно подстраивает работу [[rowset|набора строк]] к контексту использования внутри экранной формы. Тут задается взаимодействие с другими [[rowset|наборами строк]], расположенными в этой экранной форме.
Ориентировочный вид [[rowset-definition|декларативного описания]] таков:
"
datasource="<имя источника данных>"
readonly="0|1"
js_readonly=""
js_refresh=""
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=""
row.focus.path=""
evt_onnavigate="<имя запроса>"
js_onnavigate=""
mark.rowset="<имя источника данных для хранения пометок>"
mark.rowset.fieldmark="<имя поля для id пометки>"
mark.rowset.fieldtype="<имя поля для row.type>"
mark.all="0|1 (пометить все после первого refresh)"
>
...
"
value="<значение>"
js_value=""
type="string|memo|date|num|check|ref"
enabled="1|0"
js_enabled=""
(skipempty|notempty)="0|1"
/>
...
"
mode="<подрежим запроса>"
enabled="0|1"
js_enabled=""
js_caption=""
js_icon=""
caption="<текст>"
icon="<иконка>"
timeout="<время ожидания ответа>"
sync="0|1"
>
"
value="<значение>"
js_value=""
type="string|memo|date|num|check|ref"
enabled="1|0"
js_enabled=""
(skipempty|notempty)="0|1"
/>
...
...
"
enabled="0|1"
type="string|password|memo|date|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=""
readonly="0|1"
js_readonly=""
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=""
>
[
" js_value=""/>
...
...
]
"/>
...
" js_value=""/>
...
...
id1,id2,id3,...,idN
Список дочерних наборов строк
==== Атрибуты набора строк ====
Атрибуты набора строк имеют следующий смысл:
* **readonly** - если параметр равен 1 то набор строк нельзя править. Часто формируется внутри скрипта источника данных, исходя из информации о правах текущего пользователя.
* **filter** - если 1, то набор строк используется как фильтр, задающий условия для дочерних наборов строк. Его особенности:
* Никак напрямую не взаимодействует с сервером (на сервере ему не соответствует никакого источника данных)
* Полностью описывается в экранной форме
* Всегда корневой: не может иметь родительского набора строк
* Содержит одну и только одну строку, запрещена вставка и удаление строк
* Значения полей проинициалезированны параметрами default из описаний полей
* На каждое изменение любого поля автоматически производится перечитка дочерних наборов строк, такое поведение можно отменить параметром filter.autorefresh=0
==== Секция ====
Секция предназначена для описания кода JavaScript функций, нужных для вычисления динамических выражений. Обычно бывает ненужна.
==== Секция ====
Секция предназначена для описания поддерживаемых источником данных запросов. Там должны быть перечислены все допустимые для источника данных запросы. Единственное исключение - запрос **request name="change"** может быть описан внутри поля, изменение которого должно инициализировать выполнение этого запроса.
В описании запроса используются следующие атрибуты:
* **name|request** - имя запроса
* **enabled** – доступность выполнения запроса, по умолчанию доступно
* **js_ensbled** – выражение JavaScript динамически определяющее доступность выполнения запроса для текущего контекста.
* **icon** - иконка для обозначения операции
Передаваемые запросу параметры описываются в секциях **** запроса.
Особым образом обрабатываются параметры секции **** набора строк. Описанные там параметры передаются всем запросам, и их описания имеют **более высокий** приоритет, чем описание параметров внутри запроса. Эту секцию добно использовать для подстройки поведения набора строк под контекст использования в экранной форме.
Атрибуты узла **** имеют следующий смысл:
* **name|param** – имя параметра
* **value** - значение параметра
* **js_value** – выражениена JavaScript, позволяющее динамически вычислить значение параметра по контексту.
* **type** - тип для приведения
Для упрощения описания источника данных, при объявлении параметров действуют следующие умолчания:
* Значение **id="id текущей строки"** всегда автоматически вычисляется и передается запросу, его не надо специально объявлять.
* Для запроса **save** значения отредактированных и еще пока не сохраненных полей, текущей строки источника данных, автоматически вычисляются и передаются в запрос, их не надо специально объявлять.
* Для передачи запросу значения поля текущей строки источника данных достаточно в атрибуте **name** указать **имя поля**, а **js_value** не указывать.
Запрос **change** описывается внутри поля, при изменении которого вызывается. Его не нужно описывать в списке операций. Ему всегда передается текущее значение поля, в котором он объявлен
==== Описание полей источника данных ====
**Имя поля** задается значением атрибута **field**
Рассмотрим особенности реализации типов данных для полей:
* **string** – строка, просмотр и редактирование однострочным редактором
* **memo** – многострочная строка
* **num** – число в LSS формате
* **date** – дата в LSS формате
* **check** – логическое значение в LSS формате
* **refname** – поле выбирается из справочника, для описания необходимо задать **refid** – имя поля ссылки на строку справочника и **refname** – имя поля внутри справочника.
* **refid** – поле ссылки на строку справочника. Обязательно должен присутствовать дочерний узел **[** с описанием обращения к источнику данных (запрос **request="select"**), возвращающего строки справочника.
* **multilist**, **multicheck** – поле содержит значения ссылок на строки справочника, через запятую.
* допустимые значения могут быть заданы параметром **list** в виде: id1=value1;id2=value2;id3=value3;...;idN=valueN
* допустимые значения могут быть заданы параметром **listfield**, содержащем имя поля, в котором хранятся значения в виде: id1=value1;id2=value2;id3=value3;...;idN=valueN
* допустимые значения могут быть заданы посредством дочернего узла **][** с описанием обращения к источнику данных (запрос **request="refresh"**), возвращающего строки справочника. Используются параметры: **refname**, **reftext**
* допустимые значения могут быть заданы параметром **listrowset**, содержащим имя источника данных, содержащего начитанные значения. Используются параметры: **refname**, **reftext**.
Атрибут **visible** задает значение по умолчанию для видимости поля во всех строках. Если параметр не задан, то считается 1 (поле видимо). Если видимость поля зависит от контекста, то в атрибутах конкретного поля внутри конкретной строки можно задать параметр **visible**, который перешибет в этой строке значение, заданное для всей колонки.
Атрибут **readonly** задает доступность колонки для редактирования по умолчанию. Если возможность правки поля зависит от контекста, то в атрибутах конкретного поля внутри конкретной строки можно задать параметр **readonly**, который перешибет в этой строке значение, заданное для всей колонки.
Атрибут **notnull** задает требование непустого значения для поля.
Атрибут **save**="1" - принудительное сохранение строки при изменении поля
==== Раздел autoexec ====
Этот раздел позволяет периодически вызывать указанный в разделе запрос
]
...
Описание параметров
* **enabled**="1|0" - по умолчанию включен, позволяет заблокировать autoexec
* **request**="<имя запроса>" - выполняемый запрос, обычно refresh или refreshrow, но можно любой другой, например объявленный на уровне формы
* **ignoreerror**="0|1" - по умолчанию выключен, позволяет игнорировать ошибки сервера, возникающие при выполнении запроса
* **mode** - способ управления вызовом запроса
=== mode=timeout ===
В этом режиме запрос вызывается периодически, через каждые **timeout** секунд
=== mode=polling ===
В этом режиме запрос вызывается по указанию сервера, longpolling.
Для реализации такого способа достаточно Apache + PHP, оповещение работает по http
Клиент создает и держит с сервером продолжительное, заданное параметром **timeout** (30-300 секунд) http соединение. Сервер периодически, например раз в секунду, проверяет наличие интересующего клиента события. При возникновении события, возвращает клиенту строку **1** и завершается. Если за заданное **timeout** время событие не возникло, то серверное соединение завершается, ничего не сообщая клиенту. А клиент повторно создает новое соединение и ждет ответа.
...
* url - http адрес polling соединения
* timeout - время ожидания для polling соединения
* параметры param - добавляются в url polling запроса
**Пример реализации polling сервиса на PHP**
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.
...
* url - адрес socket соединения
* pingtimeout - если параметр задан, то клиент будет периодически посылать сообщение ping серверу, для проверки соединения
* параметры param - добавляются в url polling запроса
Пример реализации WebSocket сервера на Ratchet
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();