Использование класса 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);

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'],
];

Визуализация:

Simple form

Элементы форм

Элементами форм могут быть все элементы ввода 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'])\"") : '&nbsp;';
    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(..).

Методы класса Form

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>',
];

Визуализация:

Form

При визуализации такой формы с заполненными данными, для элементов формы 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

Визуализация:

Absolute busy

⚠️ **GitHub.com Fallback** ⚠️