Использование класса Form - energy-coresky/air GitHub Wiki
Класс Form позволяет строить сложные формы, используя иерархические свойства массивов PHP. С его помощью, можно организовать различные манипуляции с формами, валидацию полей форм на javascript и PHP, визуализацию объектов из базы данных – прототипов полей форм (страницы типа show, browse).
Подразумевается, что первоначально поля форм визуализируются сверху вниз, по отношению друг к другу. Формы описываются в гибридных массивах PHP, содержащих числовые и строковые ключи. Если ключ строковый, то он является прототипом атрибута "name" для элемента формы и дополнительно, может содержать правило валидации. Остальные элементы поля формы, описываются в значении к этому ключу, в числовом массиве. Если ключ числовой, а значение – строка, то эта строка, используется без преобразований, во время визуализации формы. Если ключ числовой, а значение – массив, то это поле формы, в котором отсутствует атрибут "name":
# определение формы
$form = [
'<fieldset><legend>Simple form</legend>',
'+fio' => ['Your name'], # input-type=text by default
'sex' => ['Your sex', 'select', ['male', 'female']],
['Go', 'button', 'onclick="return sky.f.ajax(box)"'],
'</fieldset>',
];
# визуализация
echo new Form($form);
<div id="f1-message"></div>
<script>
$(function() { // для валидации формы на javascript
sky.f.set('#f1', {
'fio':'+',
'+':['this field cannot be empty', '']
}, []);
});
</script>
<form method="post" enctype="multipart/form-data" id="f1">
<fieldset><legend>Simple form</legend>
<dl>
<dt>Your name</dt>
<dd><input type="text" value="" name="fio">
<span class="red"></span></dd>
</dl>
<dl>
<dt>Your sex</dt>
<dd><select name="sex">
<option value="0" selected>male</option>
<option value="1">female</option>
</select></dd>
</dl>
<dl>
<dt></dt><dd>
<input type="button" value="Go" onclick="return sky.f.ajax(box)">
</dd>
</dl>
</fieldset>
<input type="hidden" value="WmXsftK" name="_csrf">
</form>
Валидация на PHP:
(new Form($form))->validate(); # Ok or Exception
echo '$_POST: <pre>' . html(var_export($_POST, true)) . '</pre>';
Формы, также, можно определить в Yaml формате:
#.test определение формы:
- <fieldset><legend>Simple form</legend>
+fio: [Your name] # input-type=text by default
sex: [Your sex, select, [male, female]]
- [Go, button, onclick="return sky.f.ajax(box)"]
- </fieldset>
#.test
# визуализация
echo new Form(yml('+ inc(test) filename.yml'));
Возможно, вы обратили внимание, что при стиле определения полей форм, представленном выше, нельзя задекларировать два поля формы с одинаковым атрибутом "name", например, name="fname[]"
, так как ключи массивов всегда уникальны. Проблема решается с помощью метода Form::X(..)
, который всегда возвращает объект xForm и даже позволяет вставлять несколько элементов формы (подформу):
$subform = [
'fname[]' => ['First name'], # input type="text"
'lname[]' => ['Last name'],
];
$form = [
'Chess game:<br>',
'White player:<br>', Form::X($subform), # числовой ключ массива
'Black player:<br>', Form::X($subform),
];
В примере выше, определено 4 поля <input type="text" ... >
.
Для изменения порядка визуализации элементов формы (вертикально-горизонтально и обратно), используется массив в массиве:
$form = [
['Phone', [ # здесь "'Phone'," – может отсутствовать
'type' => ['', 'select', ['mobile', 'stationary']],
'num' => ['', '', 'placeholder="Enter 10 digits"'],
]],
['Go', 'submit'],
];
Визуализация:
Элементами форм могут быть все элементы ввода HTML, в том числе HTML5. В массиве, описывающем элемент формы, второй элемент - всегда тип элемента. Это может быть прототип атрибута type для тега <input>
или имя тега, например для <select>
. Первый элемент массива, обычно название поля формы или значение атрибута value, как для submit. Для тега <input type="hidden" name="xxx" value="1">
имеется сокращение: $coolform = ['xxx' => 1];
.
Также типом элемента может быть:
ni - no item, нет элемента ввода. Используется для вывода произвольного текста вместо элемента ввода, с учетом стандартного форматирования
echo new Form([[111, 'ni', 222]]);
# output:
<form method="post" enctype="multipart/form-data" id="f1">
<dl><dt>111</dt>
<dd>222</dd>
</dl><input type="hidden" value="WmXsftK" name="_csrf">
</form>
li - list item, нет элемента ввода. Подобно ni, но используется тег <li>
.
custom - custom item. Третьим параметром в массиве, должна быть callback функция для отрисовки элемента, в её параметрах объект формы и имя элемента. Пример из приложения AB.SKY:
# использование в форме:
'company_id' => [L_COMPANY, 'custom', 'draw::lookup']
# метод draw::lookup
static function lookup($form, $name) {
global $sky;
$ok = isset($form->row[$name]) && $form->row[$name];
$the_id = $ok ? $form->row[$name] : '';
$the_val = $ok ? $form->row[$obj = substr($name, 0, -3)] : '';
if ('show' == $sky->_1) # for Form::show_table(..)
return $the_id ? a($the_val, 'javascript:;', "onclick=\"run(['$obj','show&id=$the_id'])\"") : ' ';
return $form->input('hidden', $the_id, 'name="' . $name . '"')
. $form->input('text', $the_val, 'class="psearch" placeholder="type for search..."')
. a('<img src="img/a_del.gif"/>', 'javascript:;');
}
chk - элемент checkbox, в котором отсутствует атрибут name, но присутствует <input type="hidden" name=... >
. Т.е. когда элемент выбран, методом POST будет передаваться 1, а когда не выбран 0, в отличие от стандартного элемента ввода checkbox, который ничего не передает в postfields данных, когда элемент не выбран.
img - для загрузки изображений. После загрузки произвольной картинки, можно вырезать изображение фиксированных размеров. Используется класс w2/file_t.php и sky.js.
doc - аналогичный функционал, для загрузки произвольных файлов.
Детальнее смотрите Form::draw_form(..)
и Form::draw_table(..)
.
При описании форм, в массиве с фиксированным индексом -1, можно определить пользовательские правила валидации, переопределить существующие в классе по умолчанию и настроить условную валидацию. Также, можно указать префикс атрибутов "name", для всех элементов подформы:
class MyForm {
function form() {
return [
'<fieldset><legend>Юридический адрес</legend>',
Form::X($this->subform('urid_')),
'</fieldset>',
'<fieldset><legend>Фактический адрес</legend>',
Form::X($this->subform('phiz_')),
'</fieldset>',
];
}
private function subform($mk) {
$street_type = ['улица', 'проспект', 'переулок', 'площадь'];
return [
-1 => [
'!' => 'Заполните поля формы!'
[$mk],
'zip' => [t('введите 5 цифр'), '/^\d{5}$/'],
'+' => [t('обязательное поле')],
],
['Область', [
'region' => ['', 'select', sqlf('@select id, name from region')],
'/zip' => ['Почтовый индекс', '', 'style="width:90px"'],
]],
'+city' => ['Населенный пункт'],
[[
'street_type' => ['', 'select', $street_type],
'+street' => [''],
]],
'+house' => ['Дом'],
];
}
}
Встроенные валидаторы класса форм:
+ - this field cannot be empty
- - enter valid E-mail
~ - enter valid phone number
. - enter login
* - enter password
# - enter numbers
Сообщения встроенных валидаторов, можно переопределить или локализировать для разных языков мультиязычного приложения, как в примере выше. Также, можно определить пользовательские валидаторы с использованием регулярных выражений. Для определения полей форм с такими валидаторами, необходимо первым символом ключа поля формы, указать "/".
! - показать общее для формы сообщение, если javascript-валидация не прошла, и перемотать страницу вверх, к началу формы. По умолчанию, не работает, требуется явное определение.
В конфигурационном массиве, также, можно настроить условную валидацию. В ключе указывается атрибут name для элемента управления, который скрывает/показывает часть формы. А в значении массив, где указывается начало/конец части формы: 'addr' => ['phiz_region', 'phiz_house']
. Смотрите пример ниже - описание javascript метода sky.f.slide(..)
.
Javascript метод sky.f.slide(..)
можно использовать для показа/скрытия части формы. Если элементы части формы, которые могут быть скрыты, содержат валидаторы, необходимо настроить условную валидацию. Когда такая часть формы скрыта, валидация будет отменена и на уровне javascript и на уровне PHP:
$opt = ['совпадает с юридическим', 'не совпадает'];
$form = [
'<fieldset><legend>Юридический адрес</legend>',
Form::X($this->subform('urid_')),
'</fieldset>',
'addr' => ['Фактический адрес', 'radio', $opt, 0, 'onclick="return sky.f.slide(this,\'#sxd\')"'],
'<fieldset id="sxd" style="display:none"><legend>Фактический адрес</legend>',
Form::X($this->subform('phiz_')),
'</fieldset>',
];
Метод Form::X(..) : xForm
может содержать переменное количество аргументов. Если аргумент один, подразумевается, что в форму, просто вставляется подформа. Этот метод, также, может разделять холст на несколько колонок и вставлять подформы, если аргументов несколько. Первым аргументом должен быть массив, содержащий конфигурацию:
$form = Form::X(
['width="40%"', 'width="30%"', 'width="30%"'],
$subform1, $subform2, $subform3
);
Если, как в примере выше, колонок 3 или более, будет использована табличная верстка. Иначе, DIV или TABLE верстку, можно указать явно в конфигурации, в значении массива с ключом opt: 'opt' => Form::OPT_TABLE
или 'opt' => Form::OPT_DIV
. В значении массива с ключом etc, можно указать дополнительные атрибуты для тегов <table>
или <div>
: 'etc' => 'class="comment"'
. В случае табличной вёрстки, в значениях массива с числовыми ключами 0,1..N, можно указать дополнительные атрибуты для тегов <td>
, как в примере выше, смотрите Form::proc_x(..)
.
В конце концов, вместо подформы (массива PHP), например $subform3 из примера выше, можно вставить любой скаляр или объект xForm. Преобразование будет произведено автоматически: is_array($ary) or $ary = [$ary];
. Таким образом, объект xForm - это такое же описание формы как и описание формы в массиве PHP, и такое действие: Form::X(Form::X([Form::X($subform3)]))
ничего не изменит.
Методы Form::repeat_begin(string $class)
и Form::repeat_end(string $ancor)
можно использовать в формах, для повторения части формы на javascript, которая может дублироваться и содержит элементы формы, где атрибут name элементов формы - массив:
$form = [
'<fieldset><legend>All your phones</legend>',
Form::repeat_begin('clsid'),
['Phone', [
'type[]' => ['', 'select', ['mobile', 'stationary']],
'num[]' => ['', '', 'placeholder="Enter 10 digits"'],
]],
Form::repeat_end('Add phone'),
['Go', 'submit'],
'</fieldset>',
];
Визуализация:
При визуализации такой формы с заполненными данными, для элементов формы type[] и num[], в $row
должны передаваться числовые массивы. Для валидации и сбора значений, переданных в POST запросе, можно воспользоваться методом $post = Form::repeat_val(['type', 'num'])
. В параметре передается массив имен элементов формы.
Form::A(array $row, array|xForm $form) : string
- если валидация не используется, проще воспользоваться этим методом. Метод возвращает строку - визуализацию формы. Параметр $row
может содержать пустой массив (для страниц new) или ассоциативный массив (для страниц edit) - ряд значений из таблицы в БД, прототип формы.
Form::show_table(array $row, array $form)
- метод можно использовать для визуализации объекта из БД, используя верстку элементов формы. Где $row
- строка из таблицы в БД - прототип элементов формы, $form
- массив элементов формы. Пример кода из контроллера приложения AB.SKY:
<?php
class c_company extends Controller
{
// ... other class code
function j_show($id) {
global $user;
if (!$row = $this->t_company->row($id))
return 404;
$parent = $row['parent_id'];
$com = $this->t_company->listing($parent ? ['c.id=' => $parent] : ['c.parent_id=' => $id]);
$com['head'] = $parent ? L_PARENTCOMPANY : L_CHILDCOMPANIES;
$f4 = $this->is_mobile ? '' : ' - F4';
return [
'coord' => $coord = $this->m_ary->coord((object)$row, 'company'),
'show' => Form::show_table($row + ['_coord' => $coord], $this->t_company->form($row)),
'e_sale' => $this->t_sale->listing(['s.company_id=' => $id]),
'e_company' => $com,
'e_person' => $this->t_person->listing(['p.company_id=' => $id]),
'e_project' => $this->t_project->listing(['p.company_id=' => $id]),
'e_file' => $this->t_file->listing(['f.obj_id=' => $id, 'f.obj=' => 'com']),
'e_activity'=> $this->t_activity->listing(['a.company_id=' => $id]),
'upd' => 4 & $user->u_com ? draw::btn(L_UPDATE . $f4, "'company','edit&id=$id'", 1) : '',
'right' => draw::btn(t('Add to top'), ['company', $id]),
];
}
}
Часть шаблона Jet:
#.show ------------------------------------------------------------------------------
@prt()
@blue(L_COMPANY, $upd, $right)
<div style="border:1px solid #748AAC;">{!$show!}</div>
@inc(_common.map)
<p>@inc(_sale.list)
<p>@inc(.list)
<p>@inc(_person.list)
<p>@inc(_project.list)
<p>@inc(_file.list)
<p>@inc(_activity.list)
#.show
Визуализация: