Джино: хостинг и веб-сервисы

Система Orphus
Russian version
Добавить на Del.icio.us
English version
Добавить на Digg.com

 dkLab | Конструктор | HTML_FormPersister: новый взгляд на построение форм 

Карта сайта :: Форум «Лаборатории» :: Проект «Денвер»
Проект «Orphus» :: Куроводство: наблы :: Конструктор


26 апреля 2005 г.
Обсудить на форуме

Принять участие в разработке библиотеки/утилиты можно на GitHub.

При создании Web-интерфейса для управления сайтом довольно много усилий уходит на построения и обработку HTML-форм. Уже написано огромное количество различных библиотек, «упрощающих» эти процессы (см., например, HTML_Form из PEAR), однако все они обладают общими недостатками:

  • управлять внешним видом сгенерированных форм довольно сложно;
  • создание формы требует существенного участия программиста, в то время как внешний вид формы определяется дизайнером/верстальщиком.

Сегодня я выкладываю в общий доступ качественно другое решение, которое с успехом применяется на многих сайтах вот уже более двух лет. Речь о библиотеке HTML_FormPersister и используемом ей модуле HTML_SemiParser, особенности которых рассмотрены далее.

Постановка задачи

Рассмотрим пример простой формы (про подобную страницу говорят, что она отправляет данные «сама на себя»):

Листинг 1
<form method="get">
    <input type="text" name="test[text][first]">
    <input type="radio" name="test[radio]" value="first">first
    <input type="radio" name="test[radio]" value="second">second
    <input type="submit" value="Submit">
</form>
<xmp><?print_r($_GET)?></xmp>
<hr><?show_source(__FILE__)?>

Введите туда какие-нибудь данные, установите флажок и отправьте форму. В PHP-скрипте появится массив $_GET, содержащий следующие значения:

Листинг 2
Array(
  [test] => Array(
    [text] => Array(
      [first] => abc
    )
    [radio] => first
  )
)

Обратите внимание: после нажатия на кнопку Submit значения, введенные ранее в поля формы, исчезают, хотя сама форма продолжает отображаться на странице! Это совершенно не то, что интуитивно ожидает пользователь от формы такого рода.

Чтобы устранить данный недостаток, нам пришлось бы написать в скрипте примерно следующий код:

Листинг 3
<form method="get">
    <input type="text" 
        name="test[text][first]"
        value="<?=@$_REQUEST['test']['text']['first']?>"
    >
    <input type="radio" name="test[radio]" value="first"
        <?=@$_REQUEST['test']['radio']=='first'? 'checked':''?>
    >first
    <input type="radio" name="test[radio]" value="second"
        <?=@$_REQUEST['test']['radio']=='second'? 'checked':''?>
    >second
    <input type="submit" value="Submit">
</form>
<xmp><?print_r($_GET)?></xmp>
<hr><?show_source(__FILE__)?>

Наверняка многим он уже знаком, не правда ли? Довольно уродливо, но это еще не все.

  • Нужно перед выводом значений атрибутов дополнительно вызывать функцию htmlspecialchars(), потому что в противном случае любая введенная в поле кавычка сразу же испортит форму.
  • Если пользователь в первый раз зашел на страницу, может потребоваться выводить в полях формы некоторые значения по умолчанию. К примеру, нужно сделать флажок first активным, пока форма еще не отправлена.
  • Необходимо обрабатывать и другие поля формы, такие как: checkbox, textarea, select с единичным и множественным выбором и т. д.

Все эти процессы требуют прямого участия программиста — причем после того, как верстальщик сделал саму форму и определил ее дизайн. Получающийся при этом код поражает своим однообразием. Рутина, одним словом, а ей — не место в программировании.

Традиционное решение, применяемое в других библиотеках, заключается в том, что вместо тэгов формы предлагается использовать специальные PHP-функции, их генерирующие. Например, вместо <input name="test[abc]"...> пишется что-то вроде <?=tInput('test[abc]')?>, а уж функция tInput сама обрабатывает все «проблемные» ситуации (в частности, разбирает имя поля test[abc], трактуя его как позицию в массиве).

Устойчивые поля формы

Можно, конечно, начать программировать в стиле tInput(), фактически «переверстывая» все шаблоны руками программиста. Но мы пойдем другим путем: библиотека HTML_FormPersister поддерживает работу с формами на качественно другом уровне. Главная ее особенность состоит в том, что вы можете подключить библиотеку к любому скрипту, не исправив в нем ни строчки кода, и новая функциональность автоматически появится на сайте.

Взгляните еще раз на листинг 1 (этот пример в действии). Напоминаю: там приведена форма, которая после отправки данных на себя не сохраняет введенные значения. Добавим в начало этого листинга код, подключающий и активирующий HTML_FormPersister (см. работающий пример):

Листинг 4
<?php
include_once "../../lib/config.php";
include_once "HTML/FormPersister.php"; 
ob_start(array('HTML_FormPersister', 'ob_formPersisterHandler'));
?>
<form method="get">
    <input type="text" name="test[text][first]">
    <input type="radio" name="test[radio]" value="first">first
    <input type="radio" name="test[radio]" value="second">second
    <script></script>
    <input type="submit" value="Submit">
</form>
<xmp><?print_r($_GET)?></xmp>
<hr><?show_source(__FILE__)?>

Чайник 

Естественно, код подключения библиотеки можно вынести отдельно (например, в главный конфигурационный файл скрипта), тогда его не придется «тянуть» в каждом шаблоне.

Попробуйте теперь напечатать что-нибудь в форму и нажать Submit. Вы увидите, что и данные текстового поля, и положение переключателя сохранились:

Обратите внимание, что кавычки в данных остаются корректно обработанными (в действительности они превращаются в &quot; перед вставкой в атрибут тэга), и нам не нужно больше заботиться о безопасности, явно вызывая htmlspecialchars().

Чайник 

Использование функции ob_start() раскрывает метод работы HTML_FormPersister. Чуть подробнее я расскажу о нем позже, а пока замечу лишь, что модуль разбирает выходной поток скрипта (сгенерированную, но еще не отправленную в браузер HTML-страницу) в поисках элементов форм, анализирует их и подставляет требуемые атрибуты value, checked, selected и т. д. Происходит это все «на лету», и весьма быстро.

В итоге мы получаем первое преимущество библиотеки: формы, устойчивые к отправке «на себя». Примечательно, что это касается любых форм на странице. Не важно даже, каким образом они были сгенерированы.

Библиотека HTML_FormPersister корректно поддерживает «устойчивость» всех типов полей формы:

  • input-поля, включая типы: text, password, hidden, checkbox, radio.
  • Текстовые области textarea.
  • Списки select, как с единичным, так и с множественным выбором, с тэгами option и optgroup.
  • У тэга form по умолчанию проставляется атрибут action, равный URL текущей страницы.

Кроме того, соблюдаются все соглашения по именованию полей форм PHP. А именно, вы можете давать элементам имена вида array[first][second] или даже list[], не опасаясь, что работа библиотеки нарушится.

На этой странице сведены воедино все поля формы, «подвластные» модулю HTML_FormPersister. Вы можете посмотреть, как библиотека влияет на атрибуты соответствующих тэгов (в левой колонке отображается исходный код шаблона, в правой — то, что получается после обработки).

Значения полей по умолчанию

С использованием HTML_FormPersister, каждый элемент формы может иметь специальный атрибут default, в котором хранится «значение по умолчанию» для данного элемента. Оно подставляется в атрибут value (к примеру), если пользователь еще ни разу не отправил форму, а зашел на страницу впервые.

Например, написав в шаблоне:

Листинг 5
<input type="text" name="data[text]" default="something">

вы при первом заходе на страницу получите следующий тэг:

Листинг 6
<input type="text" name="data[text]" value="something">

а после ввода некоторого текста и отправки формы «на себя» — что-то вроде

Листинг 7
<input type="text" name="data[text]" value="введенный текст">

Итак, в первом случае атрибут default превратился в value, а во втором — попросту проигнорировался.

Автозаполнение форм

Должно быть очевидным, что модуль HTML_FormPersister берет данные для заполнения формы из массивов $_GET и $_POST. Это открывает интересные возможности, не связанные напрямую с идеологией «устойчивости полей». А именно, если вы в программе присвоите некоторое значение "значение" элементу $_POST['test'] (или $_GET['test']), и в форме, выводимой страницей, присутствует поле с именем test, то в нем автоматически появится текст "значение"!

Такое поведение оказывается крайне удобным при начальном заполнении формы из базы данных (для организации интерфейса редактирования какой-нибудь записи). Вам не нужно вообще менять шаблон; все, что требуется, — это заполнить массив $_POST корректными значениями.

Вот работающий пример:

Листинг 8
<?php
include_once "../../lib/config.php";
include_once "HTML/FormPersister.php"; 
ob_start(array('HTML_FormPersister', 'ob_formPersisterHandler'));
$_POST['test']['text']['first'] = 'значение';
?>
<form method="get">
    <input type="text" name="test[text][first]">
</form>
<hr><?show_source(__FILE__)?>

А вот HTML-код, который он генерирует:

Листинг 9
<form method="get" action="/chicken/nablas/demo/test/t_fill.php">
  <input type="text" name="test[text][first]" value="значение">
</form>

Автогенерация тэгов option

В большинстве случаев при выводе тэга select набор option-ов берется из некоторого массива в программе, а не задается «жестко» в шаблоне. HTML_FormPersister позволяет указать имя этого массива прямо внутри контейнера select, и тогда автоматически сгенерируется требуемый набор тэгов option (а если массив двумерный, то — набор контейнеров optgroup). Иными словами, следующие два шаблона:

Листинг 10
<select name="choose">
<?foreach ($elements as $k=>$v) {?>
  <option value="<?=htmlspecialchars($k)?>">
    <?=$v>
  </option>
<?}?>
</select>

Листинг 11
<select name="choose">elements</select>

эквивалентны, хотя второй выглядит значительно короче и нагляднее.

Предполагается, что массив $elements должен содержаться в глобальной переменной; его ключи задают величины атрибутов value тэгов option, а значения — тексты подписей.

Лирическое отступление 
Однако есть и отличие в механизмах работы этих двух шаблонов. В первом случае (при «прямом» заполнении) тэги option генерируются во время работы участка кода, в то время как во втором случае — уже после того, как скрипт окончит свою работу. Соответственно, если значение глобальной переменной $elements изменится позже обработки шаблона, в список могут попасть неверные элементы.

Атрибут confirm

Для удобства каждый input-элемент типа submit (кнопка отправки формы) может иметь атрибут confirm, в котором задается текст подтверждения отправки формы. Например, следующий шаблон:

Листинг 12
<input type="submit" confirm="Точно отправить?">

превратится в HTML-код:

Листинг 13
<input type="submit" onclick="return confirm('Точно отправить?')">

Как работает HTML_FormPersister

Функция HTML_FormPersister::process(), занимающаяся простановкой атрибутов в полях формы, в большинстве случаев запускается как обработчик выходного потока скрипта (см. ob_start()). Разбором HTML-кода в действительности занимается модуль HTML_SemiParser. Он работает на основе регулярных выражений (preg-функции), которые «вытаскивают» из текста страницы только нужные тэги и контейнеры (form, input, select и textarea), пропуская все остальное. Т.е. модуль не производит полного анализа и разбора HTML-кода, что существенно экономит время.

Чайник 

Фактически, если ваша страница не загружается пару миллионов раз на дню, накладными расходами на анализ HTML-кода в HTML_FormPersister можно пренебречь. К тому же, если на странице нет формы, то ее анализ вообще не занимает времени.

Частичный разбор HTML (SemiParser можно перевести как «полупарсер») выигрывает по скорости, однако он проигрывает по качеству результата. Точнее, существуют случаи, когда HTML_SemiParser неправильно выделяет тэги из текста страницы. Например, если тэг input будет стоять внутри контейнера <script>...</script>, то он также обработается (ибо HTML_SemiParser пока не умеет «пропускать» тэги script):

Листинг 14
<script>
// Белиберда получится!
document.writeln("<input type=\"text\" name=\""+name+"\">");
</script>

К счастью, такие случаи столь экзотичны, что в реальных приложениях ими вполне можно пренебречь. По крайней мере, моя практика показывает, что это именно так.

Другой недостаток модуля может проявиться, когда на странице используется сразу несколько различных форм. В этом случае различий между ними не делается: если обе формы имеют поля с одинаковыми именами, в каждое из них будет подставлено значения из $_GET или $_POST.

Чайник 

Обсуждение других технических особенностей модулей, к сожалению, выходят за рамки этой статьи. Вы можете обратиться к комментариям внутри кода библиотек HTML_FormPersister и HTML_SemiParser.

Резюме

Модуль HTML_FormPersister позволяет сильно упростить разработку скриптов, использующих сложные и большие формы. Особенно удобно его применять при модификации уже написанных скриптов (таких как, например, phpBB).

В этой статье я коснулся лишь аспектов создания форм, но не аспектов их приема и обработки. Думаю, второй процесс концептуально почти не связан с первым. Объединять их в одном модуле (как это делается в некоторых других библиотеках), пожалуй, было бы ошибкой.







Дмитрий Котеров, Лаборатория dk. ©1999-2016
GZip
Добавить на Del.icio.us   Добавить на Digg.com   Добавить на reddit.com