|
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. Вы увидите, что и данные текстового
поля, и положение переключателя сохранились:
Обратите внимание, что кавычки в данных остаются корректно обработанными (в действительности они
превращаются в " перед вставкой в атрибут тэга), и нам не нужно больше заботиться о безопасности,
явно вызывая 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).
В этой статье я коснулся лишь аспектов создания форм, но не аспектов
их приема и обработки. Думаю, второй процесс концептуально почти не связан с первым.
Объединять их в одном модуле (как это делается в некоторых других библиотеках), пожалуй,
было бы ошибкой.
|
|
Пользуетесь? Нравится?.. Пожертвуйте!
Пожертвование пойдет в качестве "спасибо" прямиком
на поддержание мотивации автора. А то он даже не знает, какое
количество людей пользуется той или иной библиотекой
или утилитой...
|