MySQL / 18. Обработка ввода через Web с помощью MySQL

Создание элементов формы с возможностью выбора одного значения

Задача
Форма должна содержать поле, которое предлагает пользователю много вариантов, но разрешает выбрать только один из них.

Решение
Используйте элементы с возможностью выбора единственного значения из списка (single-pick list element). К ним относятся наборы переключателей, всплывающие меню или прокручиваемые списки.

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

• Список цветов из таблицы cow_color. Их можно получить при помощи такого запроса:

mysql> SELECT color FROM cow_color ORDER BY color;

+---------------+
| color              |
+---------------+
| Black              |
| Black & White |
| Brown            |
| Cream            |
| Red                |
| Red & White   |
| See-Through |
+---------------+

Обратите внимание на то, что некоторые названия цветов содержат символ &, обладающий специальным значением в HTML. То есть при использовании для элементов списка такие значения необходимо будет кодировать по стандарту HTML. (На самом деле мы будем выполнять кодирование для всех списковых элементов формы, но эти значения наглядно демонстрируют, что кодирование – хорошая привычка.)

• Список допустимых размеров фигурок из столбца size таблицы cow_order.

Столбец относится к типу ENUM, поэтому возможные значения и значения по умолчанию можно получить при помощи предложения SHOW COLUMNS:

mysql> SHOW COLUMNS FROM cow_order LIKE 'size'\G
*************************** 1. row ***************************
Field: size
Type: enum('small','medium','large')
Null: YES
Key:
Default: medium
Extra:

• Список названий и аббревиатур штатов, которые хранятся в таблице states:

mysql> SELECT abbrev, name FROM states ORDER BY name;

+--------+---------------+
| abbrev | name           |
+--------+---------------+
| AL       | Alabama       |
| AK       | Alaska         |
| AZ       | Arizona        |
| AR       | Arkansas     |
| CA       | California     |
| CO      | Colorado     |
| CT       | Connecticut |
...

Количество значений для выбора во всех этих списках разное: 3 размера, 7 цветов и 50 штатов. Значения размеров лучше всего представить в виде переключателей, в прокручиваемом списке необходимости нет из-за небольшого количества значений для выбора. Множество цветов приемлемо представить любым из элементов для выбора единственного значения. Оно достаточно маленькое, так что набор переключателей займет не слишком много места, но и достаточно большое для того, чтобы использовать прокрутку – особенно если будут добавляться дополнительные цвета.  Список штатов, вероятно, имеет слишком много значений, чтобы быть представленным в виде переключателей, поэтому наиболее разумным его отображением будет всплывающее меню или прокручиваемый список.

Рассмотрим синтаксис для каждого из типов элементов, а затем попробуем сгенерировать их из сценариев.

Переключатели. Группа переключателей состоит из элементов <input> типа radio с одинаковыми атрибутами name. У каждого элемента также есть атрибут value. Отображаемый текст можно указать после тега <input>.

Для того чтобы пометить значение как исходный вариант выбора по умолчанию, добавьте атрибут checked. Следующая группа переключателей выводит возможные размеры фигурок коров, при этом среднее значение (medium) помечено как выбираемое по умолчанию:

<input type="radio" name="size" value="small" />small
<input type="radio" name="size" value="medium" checked="checked" />medium
<input type="radio" name="size" value="large" />large

Всплывающие меню. Всплывающее меню – это список, заключенный между тегами <select> и </select>, в котором каждый пункт меню заключен в теги <option> и </option>. У каждого элемента <option> есть атрибут value, а в теле элемента содержится видимый текст тега для вывода. Для указания выбора по умолчанию добавьте в соответствующий элемент <option> атрибут selected. Если ни один из элементов не помечен, то умолчанием становится первый элемент, как это и сделано в примере:

<select name="color">
<option value="Black">Black</option>
<option value="Black & White">Black & White</option>
<option value="Brown">Brown</option>
<option value="Cream">Cream</option>
<option value="Red">Red</option>
<option value="Red & White">Red & White</option>
<option value="See-Through">See-Through</option>
</select>

Прокручиваемые списки. Прокручиваемые списки отображаются как набор значений в окне. Список может включать больше значений, чем помещается в окне, тогда броузер выводит полосу прокрутки, с помощьюкоторой пользователь может увидеть остальные элементы. Синтаксис для прокручиваемых списков похож на синтаксис всплывающих меню, только открывающий тег <select> содержит атрибут size, указывающий, сколько строк списка должны быть видны в окне. По умолчанию список прокрутки является элементом с возможностью выбора единственного значения.

Следующий прокручиваемый список с возможностью выбора единственного значения содержит элемент для каждого штата США, при этом одновременно выводятся только шесть значений:

</select>
<select name="state" size="6">
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
...
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>

У всех этих списковых элементов есть много общего:

• Имя элемента. Когда пользователь отправляет форму, броузер сопоставляет это имя со значением, выбранным пользователем.

• Набор значений, по одному для каждого элемента списка. Так определяются значения, доступные для выбора.

• Необязательное значение по умолчанию, которое определяет, какое из значений списка будет выбрано при первоначальном отображении списка броузером.

• Видимый текст тегов (label), по одному значению для каждого элемента.

Он определяет то, что пользователь увидит при выводе формы, и отбрасывается при отсылке формы пользователем.

Для формирования спискового элемента формы на основе содержимого базы данных запустите запрос для выбора соответствующих значений и видимого текста, закодируйте все содержащиеся в них специальные символы и добавьте теги HTML для того типа списка, который вы хотите вывести. Если вам нужно указать вариант выбора по умолчанию, добавьте атрибут checked или selected для соответствующего элемента в списке.

Давайте посмотрим, как создать элементы формы для списков цветов и штатов, получаемых извлечением строк из таблицы.

В JSP вы можете вывести переключатель для цветов, применяя теги JSTL так (названия цветов используются и как значения, и как видимый текст, поэтому выводятся дважды):

<sql:query var="rs" dataSource="${conn}">
SELECT color FROM cow_color ORDER BY color
</sql:query>
<c:forEach var="row" items="${rs.rows}">
<input type="radio" name="color"
value="<c:out value="${row.color}" />"
/><c:out value="${row.color}" />

</c:forEach>

Тег <c:out> выполняет кодирование по стандарту HTML, поэтому содержащийся в некоторых значениях цветов символ & будет автоматически преобразован в &, и проблемы при отображении результирующей веб-страницы не возникнут.

Для вывода всплывающего меню запрос будет таким же, изменится лишь цикл извлечения строк:

<sql:query var="rs" dataSource="${conn}">
SELECT color FROM cow_color ORDER BY color
</sql:query>
<select name="color">
<c:forEach var="row" items="${rs.rows}">
<option value="<c:out value="${row.color}" />">
<c:out value="${row.color}" /></option>
</c:forEach>
</select>

Всплывающее меню очень легко заменить прокручиваемым списком – просто добавьте атрибут size в открывающий тег <select>. Например, для одновременного отображения трех цветов сформируйте список так:

<sql:query var="rs" dataSource="${conn}">
SELECT color FROM cow_color ORDER BY color
</sql:query>
<select name="color" size="3">
<c:forEach var="row" items="${rs.rows}">
<option value="<c:out value="${row.color}" />">
<c:out value="${row.color}" /></option>
</c:forEach>
</select>

Создание спискового элемента для штатов будет аналогичным, только в данном случае видимый текст не совпадает со значениями. Для того чтобы сделать видимый текст более понятным для пользователей, будем выводить полные названия штатов. Но значение, возвращаемое при передаче формы, должно быть аббревиатурой, так как именно она хранится в таблице cow_order.

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

<sql:query var="rs" dataSource="${conn}">
SELECT abbrev, name FROM states ORDER BY name</sql:query>
<select name="state">
<c:forEach var="row" items="${rs.rows}">
<option value="<c:out value="${row.abbrev}" />">
<c:out value="${row.name}" /></option>
</c:forEach>
</select>

Примеры на JSP используют подход, обеспечивающий индивидуальный вывод каждого значения списка. Формирование спискового элемента в CGI.pm-сценариях Perl происходит по другому принципу: сначала извлекается информация из базы данных, затем вся она передается функции, которая возвращает строку, представляющую элемент формы. Элементы единичного выбора генерируются функциями radio_group(), popup_menu() и scrolling_list().

У них есть ряд общих аргументов:

name
Указывает, как вы хотите назвать элемент.

values
Указывает значения элементов в списке. Аргумент должен быть ссылкой на массив.

default
Указывает изначально выбранный элемент в списке. Аргумент не обязателен. В наборе переключателей CGI.pm автоматически по умолчанию выбирает первый, если аргумент отсутствует. Чтобы отменить такое поведение, укажите значение по умолчанию, которое не входит в список values. (Пустая строка и undef не разрешены.)

labels
Указывает видимый текст для каждого значения. Аргумент не обязателен; если он отсутствует, то CGI.pm использует значения в качестве видимого текста. В противном случае аргумент labels должен быть ссылкой на хеш, который сопоставляет каждому значению видимый текст. Например, при формировании спискового элемента для цветов коров значения и видимый текст совпадают, поэтому в аргументе labels нет необходимости. А вот при выводе списка штатов labels будет ссылкой на хеш, сопоставляющий каждой аббревиатуре штата его полное название.

Некоторые функции принимают дополнительные аргументы. Для radio_group() можно указать аргумент linebreak, означающий вертикальный, а не горизонтальный вывод переключателей. Функция scrolling_list() принимает аргумент size, который указывает, сколько элементов должно выводиться одновременно. (Документация по CGI.pm описывает дополнительные аргументы, которые не используются в книге. Например, есть аргументы для представления переключателей в табличном формате, но я не стану так изощряться.)

Для создания элемента формы на основе цветов из таблицы cow_color необходимо извлечь их в массив:my $color_ref = $dbh->selectcol_arrayref ("SELECT color FROM cow_color ORDER BY color");

selectcol_arrayref() возвращает ссылку на массив. Значение массива можно получить по ссылке так:

my @colors = @{$color_ref};

Но аргумент values функций CGI.pm, создающих списковые элементы, в любом случае должен быть ссылкой, так что используем ссылку и для $color_ref. Для создания переключателей, всплывающего меню или прокручиваемого списка с единичным выбором вызовите функцию так:

print radio_group (-name => "color",
-values => $color_ref,
-linebreak => 1); # выводить кнопки вертикально
print popup_menu (-name => "color",
-values => $color_ref);
print scrolling_list (-name => "color",
-values => $color_ref,
-size => 3); # выводить 3 элемента одновременно

Значения и видимый текст в списке цветов совпадают, поэтому не нужно указывать аргумент labels; по умолчанию CGI.pm использует значения как видимый текст. Обратите внимание на то, что мы не выполняем HTML-кодирование цветов, хотя некоторые из них содержат символ &. В CGI.pm функции генерирования элементов формы автоматически осуществляют HTML-кодирование, в отличие от его функций создания элементов не для форм.

Для формирования списка штатов, в котором значениями являются аббревиатуры, а видимым текстом – полные названия, необходимо использовать аргумент labels. Он должен быть ссылкой на хеш, отображающий каждое значение на соответствующее название. Создаем список значений и хеш видимого текста так:

my @state_values;
my %state_labels;
my $sth = $dbh->prepare ("SELECT abbrev, name FROM states ORDER BY name");
$sth->execute ();
while (my ($abbrev, $name) = $sth->fetchrow_array ())
{
push (@state_values, $abbrev); # сохранение всех значений в массиве
$state_labels{$abbrev} = $name; # сопоставление каждому значению названия
}

Передайте получившиеся список и хеш по ссылке в функцию popup_menu() или scrolling_list() в зависимости от того, какой элемент вы хотите построить:

print popup_menu (-name => "state",
-values => \@state_values,
-labels => \%state_labels);print scrolling_list (-name => "state",
-values => \@state_values,
-labels => \%state_labels,
-size => 6); # показывать 6 элементов одновременно

Если вы используете API, в котором нет готовых функций создания элементов форм подобно CGI.pm, то можно выводить HTML по мере извлечения значений списка из MySQL или написать функции, которые будут генерировать для вас элементы списка. Далее будут рассмотрены реализации обоих способов на языках PHP и Python.

В PHP список значений таблицы cow_color можно представить в виде всплывающего меню при помощи цикла выборки и вывода записей:

$query = "SELECT color FROM cow_color ORDER BY color";
$result_id = mysql_query ($query, $conn_id);
print ("<select name=\"color\">\n");
while (list ($color) = mysql_fetch_row ($result_id))
{
$color = htmlspecialchars ($color);
print ("<option value=\"$color\">$color</option>\n");
}
mysql_free_result ($result_id);
print ("</select>\n");

Код на Python, делающий то же самое, выглядит так:

query = "SELECT color FROM cow_color ORDER BY color"
cursor = conn.cursor ()
cursor.execute (query)
print "<select name=\"color\">"
for (color, ) in cursor.fetchall ():
color = cgi.escape (color, 1)
print "<option value=\"%s\">%s</option>" % (color, color)
cursor.close ()
print "</select>"

Для списка штатов необходимы отличающиеся друг от друга видимый текст и значения, поэтому код становится несколько сложнее. На PHP он будет таким:

$query = "SELECT abbrev, name FROM states ORDER BY name";
$result_id = mysql_query ($query, $conn_id);
print ("<select name=\"state\">\n");
while (list ($abbrev, $name) = mysql_fetch_row ($result_id))
{
$abbrev = htmlspecialchars ($abbrev);
$name = htmlspecialchars ($name);
print ("<option value=\"$abbrev\">$name</option>\n");
}
mysql_free_result ($result_id);
print ("</select>\n");А на Python таким:
query = "SELECT abbrev, name FROM states ORDER BY name"
cursor = conn.cursor ()
cursor.execute (query)
print "<select name=\"state\">"
for (abbrev, name) in cursor.fetchall ():
abbrev = cgi.escape (abbrev, 1)
name = cgi.escape (name, 1)
print "<option value=\"%s\">%s</option>" % (abbrev, name)
cursor.close ()
print "</select>"

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

make_radio_group (name, values, labels, default, vertical)
make_popup_menu (name, values, labels, default)
make_scrolling_list (name, values, labels, default, size, multiple)

У функций есть несколько общих аргументов:

name
Указывает имя элемента формы.

values
Массив или список значений для записей элемента.

labels
Еще один массив с видимым текстом для каждого значения. Два массива должны иметь одинаковые размеры. (Если вы хотите использовать значения в качестве видимого текста, просто передайте функции один и тот же массив дважды.)

default
Указывает начальное значение элемента формы. Это должно быть скалярное значение для всех функций, кроме scrolling_list(). Эта функция будет написана так, чтобы обрабатывать списки с возможностью как единичного, так и множественного выбора, поэтому ее значение default может быть и скаляром, и массивом. Если умолчание не задано, передайте значение, которое не содержится в массиве values; обычно используется пустая строка.

У некоторых функций есть дополнительные аргументы, которые применяются только к определенным типам элементов:

• vertical применяется к группам переключателей. Предписывает расположить переключатели в вертикальном, а не горизонтальном порядке.

• Аргументы size и multiple применяются к прокручиваемым спискам: size указывает количество видимых элементов списка, а multiple устанавливается в значение «истина», если список допускает множественный выбор.

Мы рассмотрим реализацию некоторых функций формирования списков, а код всех таких функций хранится в каталоге lib дистрибутива recipes. Все они, подобно функциям модуля CGI.pm, автоматически выполняют HTML-кодирование для значений аргументов, встраиваемых в список.

В PHP можно написать функцию создания группы переключателей: make_radio_group() так:

function make_radio_group ($name, $values, $labels, $default, $vertical)
{
if (!is_array ($values))
return ("make_radio_group: values argument must be an array");
if (!is_array ($labels))
return ("make_radio_group: labels argument must be an array");
if (count ($values) != count ($labels))
return ("make_radio_group: value and label list size mismatch");
$str = "";
for ($i = 0; $i < count ($values); $i++)
{
# выбрать элемент, если он соответствует значению по умолчанию
$checked = ($values[$i] == $default ? " checked=\"checked\"" : "");
$str .= sprintf (
"<input type=\"radio\" name=\"%s\" value=\"%s\"%s />%s",
htmlspecialchars ($name),
htmlspecialchars ($values[$i]),
$checked,
htmlspecialchars ($labels[$i]));
if ($vertical)
$str .= "
"; # вывести элементы вертикально
$str .= "\n";
}
return ($str);
}

Функция выполняет предварительную проверку аргументов, затем создает элемент формы в виде строки, которую и возвращает. Чтобы использовать функцию для представления цветов фигурок коров, вызовем ее после извлечения записей из таблицы cow_color:

$values = array ();
$query = "SELECT color FROM cow_color ORDER BY color";
$result_id = mysql_query ($query, $conn_id);
if ($result_id)
{
while (list ($color) = mysql_fetch_row ($result_id))
$values[] = $color;
mysql_free_result ($result_id);
}
print (make_radio_group ("color", $values, $values, "", TRUE));

Массив $values передается функции дважды, так как он используется и для значений, и для видимого текста.

Если вы хотите вывести всплывающее меню, используйте другую функцию:

function make_popup_menu ($name, $values, $labels, $default)
{
if (!is_array ($values))
return ("make_popup_menu: values argument must be an array");
if (!is_array ($labels))
return ("make_popup_menu: labels argument must be an array");
if (count ($values) != count ($labels))
return ("make_popup_menu: value and label list size mismatch");
$str = "";
for ($i = 0; $i < count ($values); $i++)
{
# выбрать элемент, если он соответствует значению по умолчанию
$checked = ($values[$i] == $default ? " selected=\"selected\"" : "");
$str .= sprintf (
"<option value=\"%s\"%s>%s</option>\n",
htmlspecialchars ($values[$i]),
$checked,
htmlspecialchars ($labels[$i]));
}
$str = sprintf (
"<select name=\"%s\">\n%s</select>\n",
htmlspecialchars ($name),
$str);
return ($str);
}

У функции make_popup_menu() нет параметра $vertical, но в остальном она вызывается точно так же, как make_radio_group():

print (make_popup_menu ("color", $values, $values, ""));

Функция make_scrolling_list() похожа на make_popup_menu(), поэтому я не привожу здесь ее реализацию. Если вы хотите вызвать ее для формирования списка с возможностью выбора единственного элемента, передайте ей те же аргументы, что и make_popup_menu(), укажите, сколько строк должно отображаться одновременно и добавьте аргумент multiple со значением FALSE:

print (make_scrolling_list ("color", $values, $values, "", 3, FALSE));

Список штатов использует различные наборы данных для значений и видимого текста. Извлечем их так:

$values = array ();
$labels = array ();
$query = "SELECT abbrev, name FROM states ORDER BY name";
$result_id = mysql_query ($query, $conn_id);
if ($result_id)
{while (list ($abbrev, $name) = mysql_fetch_row ($result_id))
{
$values[] = $abbrev;
$labels[] = $name;
}
mysql_free_result ($result_id);
}

Затем используем видимый текст и значения для формирования необходимого списка:

print (make_popup_menu ("state", $values, $labels, ""));
print (make_scrolling_list ("state", $values, $labels, "", 6, FALSE));

Реализация функций на Python аналогична версиям на PHP. Например, функция make_popup_menu() выглядит так:

def make_popup_menu (name, values, labels, default):
if type (values) not in (types.ListType, types.TupleType):
return ("make_popup_group: values argument must be a list")
if type (labels) not in (types.ListType, types.TupleType):
return ("make_popup_group: labels argument must be a list")
if len (values) != len (labels):
return ("make_popup_group: value and label list size mismatch")
str = ""
for i in range (len (values)):
value = values[i]
label = labels[i]
# проверить, являются ли значения и видимый текст строками
if type (value) is not types.StringType:
value = `value`
if type (label) is not types.StringType:
label = `label`
# выбрать элемент, если он соответствует значению по умолчанию
if type (default) is not types.StringType:
default = `default`
if value == default:
checked = " selected=\"selected\""
else:
checked = ""
str = str + \
"<option value=\"%s\"%s>%s</option>\n" \
% (cgi.escape (value, 1),
checked,
cgi.escape (label, 1))
if type (name) is not types.StringType:
name = `name`
str = "<select name=\"%s\">\n%s</select>\n" \
% (cgi.escape (name, 1), str)
return (str)Для представления в форме цветов коров извлекаем их из таблицы:
values = []
query = "SELECT color FROM cow_color ORDER BY color"
cursor = conn.cursor ()
cursor.execute (query)
for (color, ) in cursor.fetchall ():
values.append (color)
cursor.close ()

Затем преобразуем список в элемент формы:

print make_radio_group ("color", values, values, "", 1)
print make_popup_menu ("color", values, values, "")
print make_scrolling_list ("color", values, values, "", 3, 0)

Для вывода списка штатов извлекаем их названия и аббревиатуры:

values = []
labels = []
query = "SELECT abbrev, name FROM states ORDER BY name"
cursor = conn.cursor ()
cursor.execute (query)
for (abbrev, name) in cursor.fetchall ():
values.append (abbrev)
labels.append (name)
cursor.close ()

Затем передаем соответствующей функции:

print make_popup_menu ("state", values, labels, "")
print make_scrolling_list ("state", values, labels, "", 6, 0)

Есть одна вещь, которую функции на Python делают, а их аналоги на PHP – нет: явное преобразование значений аргументов, встраиваемых в список в строковом формате. Это необходимо, так как метод cgi.escape() порождает исключение, если вы пытаетесь использовать его для HTML-кодирования нестрокового значения.

Пока что мы говорили об извлечении строк из таблиц cow_color и states и их преобразовании в элементы формы. Для онлайн-приложения для заказа фигурок коров необходим еще один элемент формы – поле, указывающее размер статуэтки. Разрешенные значения этого поля определяются столбцом size таблицы cow_order. Этот столбец относится к типу ENUM, так что для получения допустимых значений элемента формы следует получить определение столбца и выделить из него нужную информацию.

К счастью, большая часть работы по решению этой задачи уже была проделана в рецепте 9.6, где создавались функции для вывода метаданных столбцов ENUM и SET. Например, в Perl для получения метаданных столбца size вызовите функцию get_enumorset_info():

my $size_info = get_enumorset_info ($dbh, "cow_order", "size");

Результирующее значение $size_info – это ссылка на хеш, содержащий множество элементов, два из которых нас сейчас интересуют:

$size_info->{values}
$size_info->{default}

Член values представляет собой ссылку на разрешенные значения перечислимого типа, а default – это значение столбца по умолчанию. Формат информации позволяет непосредственно преобразовать ее в элемент формы, например в группу переключателей или всплывающее меню:

print radio_group (-name => "size",
-values => $size_info->{values},
-default => $size_info->{default},
-linebreak => 1); # расположить кнопки вертикально
print popup_menu (-name => "size",
-values => $size_info->{values},
-default => $size_info->{default});

Значением по умолчанию является medium, это значение будет выбрано при открытии формы броузером.

Функция извлечения метаданных для PHP возвращает ассоциативный массив. Используем его для генерирования элементов формы на основе метаданных столбца size так:

$size_info = get_enumorset_info ($conn_id, "cow_order", "size");
print (make_radio_group ("size",
$size_info["values"],
$size_info["values"],
$size_info["default"],
TRUE)); # расположить элементы вертикально
print (make_popup_menu ("size",
$size_info["values"],
$size_info["values"],
$size_info["default"]));

Версия функции на Python возвращает словарь, который используется аналогично:

size_info = get_enumorset_info (conn, "cow_order", "size")
print make_radio_group ("size",
size_info["values"],
size_info["values"],
size_info["default"],
1)
print make_popup_menu ("size",
size_info["values"],
size_info["values"],
size_info["default"])

Если вы именно так используете значения ENUM для создания списковых элементов, то они выводятся в том порядке, в котором были указаны в определении столбца. В определении столбца size значения представлены в удобном для отображения порядке (small, medium, large), если же вас не устраивает порядок столбцов в определении, отсортируйте их.

Чтобы проиллюстрировать создание элементов формы для JSP-страниц на основе метаданных столбца, я буду использовать функцию, встраиваемую в страницу. Более удачным способом является создание в библиотеке тегов пользовательского действия (custom action), которое соответствует классу, возвращающему информацию, но создание пользовательских тегов выходит за рамки данной книги. Поэтому в примерах будут применяться следующие приемы:

• Используем теги JSTL для выполнения запроса SHOW COLUMNS с целью получения определения столбца ENUM, затем перемещаем определение в контекст страницы.

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

• Обращаемся к массиву при помощи итератора JSTL, который выводит каждое из значений как элемент списка. Каждое значение сравнивается со значением столбца по умолчанию и, в случае совпадения, значение помечается как первоначально выбранное.

Функция, извлекающая разрешенные значения из определения столбца ENUM или SET, называется getEnumOrSetValues(). Поместим ее в страницу JSP так:

<%@ page import="java.util.*" %>
<%@ page import="org.apache.oro.text.perl.*" %>
<%!
// объявить метод класса для выделения значений ENUM/SET. typeDefAttr - имя атрибута
// контекста страницы, который содержит определение типа столбца
// valListAttr - имя атрибута контекста страницы, в который будет
// помещен список значений столбца
void getEnumOrSetValues (PageContext ctx,
String typeDefAttr,
String valListAttr)
{
Perl5Util util = new Perl5Util ();
String typeDef = ctx.getAttribute (typeDefAttr).toString ();
// убрать начальное "enum(" и конечное ")", чтобы остался список
// закавыченных значений, разделенных запятыми
String qValStr = util.substitute ("s/^(enum|set)\\((.*)\\)$/$2/", typeDef);List quotedVal = new ArrayList ();
List unquotedVal = new ArrayList ();
// разбить строку по запятым для формирования списка закавыченных значений
util.split (quotedVal, "/,/", qValStr);
for (int i = 0; i < quotedVal.size (); i++)
{
// убрать кавычки из каждого значения
String s = quotedVal.get (i).toString ();
s = util.substitute ("s/^'(.*)'$/$1/g", s);
unquotedVal.add (s);
}
ctx.setAttribute (valListAttr, unquotedVal);
}
%>

Функция принимает три аргумента:

• Объект контекста страницы.

• Имя атрибута страницы, который содержит определение столбца. Это «вход» функции.

• Имя атрибута страницы, в который помещается результирующий массив разрешенных значений столбца. Это «выход» функции.

Для формирования спискового элемента на основе столбца size начнем с получения метаданных столбца: извлечем список значений столбца в переменную JSTL values, а значение по умолчанию – в переменную default:

<sql:query var="rs" dataSource="${conn}">
SHOW COLUMNS FROM cow_order LIKE 'size'
</sql:query>
<c:set var="typeDef" scope="page" value="${rs.rowsByIndex[0]}" />
<% getEnumOrSetValues (pageContext, "typeDef", "values"); %>
<c:set var="default" scope="page" value="${rs.rowsByIndex[0]}" />

Затем используем список значений и значение по умолчанию для построения элемента формы. Например, создадим группу переключателей:

<c:forEach var="val" items="${values}">
<input type="radio" name="size"
value="<c:out value="${val}" />"
<c:if test="${val == default}">checked="checked"</c:if>
/><c:out value="${val}" />

</c:forEach>
Или всплывающее меню:
<select name="size">
<c:forEach var="val" items="${values}">
<option
value="<c:out value="${val}" />"
<c:if test="${val == default}">selected="selected"</c:if>
>
<c:out value="${val}" /></option></c:forEach>
</select>

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

Сценарий CGI.pm мог бы делать это так:

my $table_ref = $dbh->selectcol_arrayref ("SHOW TABLES");
print scrolling_list (-name => "table",
-values => $table_ref,
-size => 10); # показывать 10 записей одновременно

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

<sql:query var="rs" dataSource="${conn}">
SELECT
DATE_SUB(CURDATE(),INTERVAL 5 DAY),DATE_SUB(CURDATE(),INTERVAL 4 DAY),
DATE_SUB(CURDATE(),INTERVAL 3 DAY),
DATE_SUB(CURDATE(),INTERVAL 2 DAY),
DATE_SUB(CURDATE(),INTERVAL 1 DAY),
CURDATE()
</sql:query>

Затем использовать даты для формирования спискового элемента:

<c:set var="dateList" value="${rs.rowsByIndex[0]}" />
<c:forEach var="date" items="${dateList}">
<input type="radio" name="date"
value="<c:out value="${date}" />"
/><c:out value="${date}" />

</c:forEach>

(Конечно, если API обеспечивает простой способ вычисления дат, то может быть эффективнее выводить список дат с клиентской стороны, не отправляя запрос на сервер MySQL.)

Статьи по MySQL на эту тему:

Ведение журнала Apache с помощью MySQL
Выполнение поиска и получение результатов
Журнал доступа к веб-странице
Загрузка в форму записи базы данных
Использование ввода через Web для формирования запросов