Многостраничные диалоги в одном HMTL-документе
Сергей Кривошеев
Достаточно часто при создании Internet-проектов формы для ввода большого количества данных (например, анкеты новых пользователей) разделяют на документы, содержащие меньшее число элементов управления. К примеру, на первом экране пользователь указывает свою контактную информацию, затем - адрес, возраст, навыки. При этом для перехода к каждой новой форме предварительно необходимо передать введенную ранее информацию на сервер. Если же пользователю необходимо вернуться на несколько экранов назад (предположим, он вспомнил, что ошибся в написании адреса электронной почты), то это требует последовательного перехода по всем заранее заполненным формам, что может повлечь за собой потерю предварительно введенной информации. О времени, которое тратится на повторную отработку скрипта, генерирующего страницу (-цы) с формой (-ми), я уже и не говорю.
В рамках данной статьи я бы хотел рассказать о том, как можно реализовать многостраничные диалоги в рамках одного HTML-документа. Тогда, с одной стороны, с точки зрения пользователя форма останется многостраничной и каждый новый экран не будет перегружен элементами ввода, а с другой - данные на сервер будут передаваться все сразу, без разделения по этапам. Такой подход позволит посетителю быстро перейти на интересующую его часть формы и избавит его от необходимости вводить информацию в том порядке, который установлен разработчиком сайта.
Предположим, нам необходимо разработать механизм, позволяющий посетителю нашего сайта заполнить некую анкету. HTML-текст этой формы представлен ниже.
<HTML><HEAD>
<FORM name="myForm" method = "get" action="processform.php">
</HEAD>
<BODY>
<P>Имя:<BR><INPUT size=30 name=myName>
<P>Адрес электронной почты:<BR><INPUT size=30 name=myEmail>
<P>Имя пользователя:<BR><INPUT size=30 name=myUserid>
<P>Пароль:<BR><INPUT type=password size=30 name=myPassword>
<P>Адрес1:<BR><INPUT size=30 name=myAddress1>
<P>Адрес2:<BR><INPUT size=30 name=myAddress2>
<P>Адрес3:<BR><INPUT size=30 name=myAddress3>
<P>Страна: <BR><SELECT name=myCountry>
<OPTION selected>Россия
<OPTION>США
<OPTION>Австралия</OPTION></SELECT>
<P>Возраст:
<BR><INPUT type=radio CHECKED value="0 - 17" name=myAge> 0 - 17
<BR><INPUT type=radio value="16 - 21" name=myAge> 16 - 21
<BR><INPUT type=radio value="22 - 30" name=myAge> 22 - 30
<BR><INPUT type=radio value="30 - 45" name=myAge> 30 - 45
<BR><INPUT type=radio value="45 - 60" name=myAge> 45 - 60
<BR><INPUT type=radio value=60+ name=myAge> 60+
<P>Навыки
<BR><INPUT type=checkbox CHECKED name=Java> - Java
<BR><INPUT type=checkbox name=JavaScript> - JavaScript
<BR><INPUT type=checkbox name=VBScript> - VBScript
<BR><INPUT type=checkbox name=ASP> - ASP
<BR><INPUT type=checkbox name=HTML> - HTML
<p>
<INPUT type=submit value=Отправить!>
</FORM>
</BODY></HTML>
Как видно из приведенного выше кода, указываемые пользователем данные можно разделить на следующие группы:
- контактная информация;
- адрес;
- возраст;
- навыки.
Такая форма лишена возможности привнесения в процесс ввода данных интерактивности. К примеру, на втором этапе (указание адреса) мы узнаем, что посетитель проживает за границей. Вполне вероятно, что в реальной ситуации данный факт может существенно повлиять на набор и содержание последующих вопросов. При этом, если такой механизм реализовать в рамках одной большой формы, пользователь по изменению интерфейса сразу поймет, что причиной тому - вводимая им информация. А если же экраны с элементами ввода будут разнесены, то внешний вид последующих диалогов не будет ассоциироваться с указанной ранее информацией. Кроме того, каждый экран объединяет элементы ввода, объединенные по смыслу. При использовании соответствующих технологий (CGI-скрипты, исполняемые на сервере), существует возможность организовать ввод данных как обработку нескольких отдельных форм, в том числе и динамически изменяемые набор и последовательность форм, предлагаемых пользователю. К примеру, если посетитель вашего сайта уже указывал о себе какую-либо информацию, то можно не заставлять его повторно вводить одни и те же данные.
Но вернемся к нашему примеру. Рассмотренную выше форму мы можем разделить на четыре логически замкнутые группы элементов ввода: рисунки 1, 2, 3, 4.
Если обработка форм осуществляется с использованием cgi-скриптов, то исходный код для этих HTML-документов мог бы выглядеть примерно так:
short1.htm
<HTML><HEAD>
<FORM name="myForm" action="processform.php">
</HEAD>
<BODY>
<P>Имя:<BR><INPUT size=30 name=myName>
<P>Адрес электронной почты:<BR><INPUT size=30 name=myEmail>
<P>Имя пользователя:<BR><INPUT size=30 name=myUserid>
<P>Пароль:<BR><INPUT type=password size=30 name=myPassword>
<P><INPUT type=hidden name=step value=1>
<INPUT type=submit value=Отправить!>
</FORM>
</P></BODY></HTML>
short2.htm
<HTML><HEAD>
<FORM name="myForm" action="processform.php">
</HEAD>
<BODY>
<P>Адрес1:<BR><INPUT size=30 name=myAddress1>
<P>Адрес2:<BR><INPUT size=30 name=myAddress2>
<P>Адрес3:<BR><INPUT size=30 name=myAddress3>
<P>Страна: <BR><SELECT name=myCountry>
<OPTION selected>Россия
<OPTION>США
<OPTION>Австралия</OPTION></SELECT>
<P><INPUT type=hidden name=step value=2>
<INPUT type=submit value=Отправить!>
</FORM>
</P></BODY></HTML>
short3.htm
<HTML><HEAD>
<FORM name="myForm" action="processform.php">
</HEAD>
<BODY>
<BR><INPUT type=radio CHECKED value="0 - 17" name=myAge> 0 - 17
<BR><INPUT type=radio value="16 - 21" name=myAge> 16 - 21
<BR><INPUT type=radio value="22 - 30" name=myAge> 22 - 30
<BR><INPUT type=radio value="30 - 45" name=myAge> 30 - 45
<BR><INPUT type=radio value="45 - 60" name=myAge> 45 - 60
<BR><INPUT type=radio value=60+ name=myAge> 60+
<INPUT type=hidden name=step value=3>
<INPUT type=submit value=Отправить!>
</FORM>
</P></BODY></HTML>
short4.htm
<HTML><HEAD>
<FORM name="myForm" action="processform.php">
</HEAD>
<BODY>
<P>Навыки
<BR><INPUT type=checkbox CHECKED name=Java> - Java
<BR><INPUT type=checkbox name=JavaScript> - JavaScript
<BR><INPUT type=checkbox name=VBScript> - VBScript
<BR><INPUT type=checkbox name=ASP> - ASP
<BR><INPUT type=checkbox name=HTML> - HTML
<P><INPUT type=hidden name=step value=2>
<INPUT type=submit value=Отправить!>
</FORM>
</P></BODY></HTML>
Однако такой подход содержит в себе несколько подводных камней. Во-первых, необходимо реализовать механизм пошаговой обработки информации в рамках работы с одним пользователем (сессии). Данный механизм можно реализовать, в частности, с применением cgi-скриптов или использованием cookies, а также скрытых полей формы. Кроме того, как уже упоминалось выше, при переходе пользователя к предыдущим формам все вводимые данные должны быть указаны заново.
Применив Dynamic HTML (DHTML), мы сможем объединить несколько страниц в один документ, причем пользователь всегда будет видеть только одну из них, а переход между формами может быть реализован, к примеру, путем нажатий на соответствующие кнопки вверху экрана. При таком подходе пользователь может заполнять данные произвольно, самостоятельно выбирая, в какой последовательности передавать информацию, имея при этом возможность вернуться на предыдущую страницу без риска потерять уже введенные данные.
С использованием механизма JavaScript мы записываем форму в тег SPAN, замещая при этом диалог, отображаемый в данный момент на экране. Перед уничтожением формы нам необходимо сохранить всю введенную пользователем информацию, для чего используются обработчики событий нажатий на соответствующие кнопки.
После того как пользователь посчитает, что вся информация указана верно, он нажимает на кнопку "Отправить!" и все данные передаются на сервер за один раз. Это достигается путем использования еще одной формы, все элементы которой, кроме самой кнопки "Отправить!", скрыты от пользователя. При этом каждый из элементов ввода такой формы ставится в соответствие вводимому пользователем элементу информации.
Приведенный ниже тег SPAN послужит контейнером для форм. В начальном состоянии контейнер пуст, однако, как уже говорилось выше, мы сможем использовать DHTML для изменения его содержания.
<span id="myTab" style="position:absolute"></span>
Теперь опишем скрытую форму, которая будет использоваться для передачи введенных пользователем данных на сервер.
<form name="hiddenForm" method="get" action="processform.php">
<input type="hidden" value="" name="myName">
<input type="hidden" value="" name="myEmail">
<input type="hidden" value="" name="myUserid">
<input type="hidden" value="" name="myPassword">
<input type="hidden" value="" name="myAddress1">
<input type="hidden" value="" name="myAddress2">
<input type="hidden" value="" name="myAddress3">
<input type="hidden" value="0" name="myCountry">
<input type="hidden" value="" name="myAge">
<input type="hidden" value="" name="Java">
<input type="hidden" value="" name="JavaScript">
<input type="hidden" value="" name="VBScript">
<input type="hidden" value="" name="ASP">
<input type="hidden" value="" name="HTML">
<input type="button" value="Имя" onClick="update(tab);show(tab=1)">
<input type="button" value="Адрес" onClick="update(tab);show(tab=2)">
<input type="button" value="Возраст" onClick="update(tab);show(tab=3)">
<input type="button" value="Навыки" onClick="update(tab);show(tab=4)">
<input type="submit" value="Отправить!">
</form>
Описанная выше форма содержит несколько видимых элементов: кнопка для передачи данных на сервер и четыре кнопки, нажатие на которые приводит к переходу на один из диалогов (контактная информация, адрес, возраст, навыки). При этом последовательно выполняются две функции: update() и show(). Обратите внимание, что все поля формы имеют тип hidden.
Следующим этапом будет создание HTML-кода для каждой мини-формы. Во-первых, опишем массивы данных, входящие в состав выпадающего списка (страны), списка переключателей (возраст) и наборов опций (навыки).
var theCountries = ['Россия','США','Австралия'];
var theAges = ['0 - 17','16 - 21','22 - 30','30 - 45','45 - 60','60+'];
var theSkills = ['Java','JavaScript','VBScript',-'ASP','HTML'];
Обратите внимание, что использование массивов для описания форм позволяет быстро изменять набор и количество значений, входящих в состав выпадающих списков и различных перечислений. JavaScript-код использует описанные выше массивы при генерации соответствующих форм.
Для создания формы используется функция show(), в рамках которой в зависимости от значения передаваемого параметра (номер нажатой кнопки) формируется тот или иной диалог. Для этого применяется конструкция switch.
function show(n) {
var output = '<form name="myForm">';
switch(n) {
case 1:
// Генерация формы для указания контактной иформации
output += '\n<p>Имя:<br>';
output += '\n<input type="text" name="myName" value="' +
document.hiddenForm.myName.value + '" size="30">';
output += '\n<p> Адрес электронной почты:<br>';
output += '\n<input type="text" name="myEmail" value="' +
document.hiddenForm.myEmail.value + '" size="30">';
output += '\n<p> Имя пользователя:<br>';
output += '\n<input type="text" name="myUserid" value="' +
document.hiddenForm.myUserid.value + '" size="30">';
output += '\n<p>Пароль:<br>';
output += '\n<input type="password" name="myPassword" value="' +
document.hiddenForm.myPassword.value +
'" size="30">';
break;
case 2:
// Генерация формы для ввода адреса
output += '\n<p>Адрес1:<br>';
output += '\n<input type="text" name="myAddress1" value="' +
document.hiddenForm.myAddress1.value + '" size="30">';
output += '\n<p>Адрес2:<br>';
output += '\n<input type="text" name="myAddress2" value="' +
document.hiddenForm.myAddress2.value + '" size="30">';
output += '\n<p>Адрес3:<br>';
output += '\n<input type="text" name="myAddress3" value="' +
document.hiddenForm.myAddress3.value + '" size="30">';
output += '\n<p>Страна:';
output += '\n<br><select name="myCountry">';
for (var i=0; i<theCountries.length; i++) {
if (document.hiddenForm.myCountry.value == theCountries[i])
output += '\n<option selected>' + theCountries[i];
else
output += '\n<option>' + theCountries[i];
}
output += '\n<\/select>';
break;
case 3:
// Отображаем переключатели для указания возраста
output += '\n<p>Возраст:';
for (var i=0; i<theAges.length; i++) {
if (document.hiddenForm.myAge.value == theAges[i])
output += '\n<br><input checked type="radio" name="myAge"
value="' + theAges[i] + '"> ' + theAges[i];
else
output += '\n<br><input type="radio" name="myAge"
value="' + theAges[i] + '"> ' + theAges[i];
}
break;
case 4:
// Поля ввода для выбора навыков
output += '\n<p>Skills:'
for (var i=0; i<theSkills.length; i++) {
if (document.hiddenForm.elements[the-Skills[i]].value == 'on')
output += '\n<br><input checked type="checkbox" name="' +
theSkills[i] + '"> - ' + theSkills[i];
else
output += '\n<br><input type="checkbox" name="' +
theSkills[i] + '"> - ' + theSkills[i];
}
break;
}
output += '\n<\/form>';
}
Значение каждого из отображаемых полей совпадает с соответствующим значением поля скрытой формы. Для текстовых полей используется свойство value, для выпадающего списка - атрибут selected, для переключателей - атрибут checked.
Мы должны иметь возможность обновлять содержимое тега SPAN. Это зависит от версии броузера, который использует пользователь. Положительный результат работы описываемого в данной статье JavaScript-кода можно ожидать в случае применения пользователем броузеров Netscape Navigator 4, а также Internet Explorer 4 и выше. Если используется Internet Explorer, то мы можем изменить значение тега SPAN путем изменения свойства innerHTML. Для Netscape Navigator следует использовать объект document.layers:
if (document.all)
document.all('myTab').innerHTML = output;
else if (document.layers) {
document.layers['myTab'].document.-open();
document.layers['myTab'].document.writeln-(output);
document.layers['myTab'].document.-close();
}
}
Единственное, что нам осталось, - это написать код, который сохранял бы данные, внесенные пользователем в одну форму, прежде чем она будет заменена другой. Эту задачу решает функция update(). По указанному номеру диалога функция определяет, какая информация и какого типа была введена пользователем и должна быть сохранена для дальнейшего использования. Для получения данных используются объект document.all или document.layers в зависимости от используемого типа броузера (Internet Explorer или Netscape Navigator соответственно).
function update(n) {
var what;
if (document.all)
what = document.all['myTab'].myForm.elements;
else if (document.layers)
what = document.layers['myTab'].document.myForm.-elements;
for (var i=0, j=what.length; i<j; i++) {
var myType = (what[i].type).toLowerCase(), myName = what[i].name;
if (myType == 'radio' && what[i].checked)
document.hiddenForm.elements[my-Name].value = what[i].value;
if (myType == 'checkbox')
document.hiddenForm.elements[my-Name].value = ((what[i].checked) ?
what[i].value : '');
if (myType == 'password' || myType == 'text' || myType == 'textarea')
document.hiddenForm.elements[myName].value = what[i].value;
if (myType == 'select-one')
document.hiddenForm.elements[my-Name].value =
what[i].options[what[i].selectedIndex-].text;
}
}
Информация в скрытой форме обновляется путем использования имен видимых элементов ввода (имена у скрытых и видимых полей должны полностью совпадать).
Итак, весь код написан. Осталось обеспечить возможность первоначальной инициализации документа при его загрузке. Для этого можно воспользоваться событием onLoad тега BODY:
<body onLoad="if (document.all || document.layers) show(tab=1)">
Казалось бы, все сделано. Однако пришло время немного поговорить о W3C (The World Wide Web Consortium) Document Object Model (объектная модель документов - DOM), и, уверяю вас, этот разговор повлияет на конечный код нашего HTML-документа.
Компании Netscape и Microsoft в четвертых версиях своих броузеров Document Object Model реализовали по-разному. Microsoft - с использованием объекта document.all, а Netscape - document.layers. С тех пор, если разработчик хотел добиться одинакового отображения своих страниц в различных броузерах, ему приходилось писать двойной код - для двух версий DOM. Сам факт поддержки DOM различными броузерами позволяет разработчику создавать документы, понятные всем приложениям просмотра HTML-страниц, однако расхождения, которые имеют место между DOM Microsoft и Netscape, не позволяют WEB-мастеру использовать все возможности, предоставляемые ими. Приходится либо отказываться от каких-либо функций, либо сознательно идти на невозможность полноценного просмотра страниц во всех версиях броузеров.
Конечно, хотелось бы, чтобы наша форма одинаково хорошо работала во всех без исключения программах просмотра HTML-страниц. В связи с этим следует ввести еще несколько дополнений к тому коду, который мы рассмотрели чуть выше.
Так как атрибуты обращения к объектам в IE и Navigator отличаются, приходится прибегать к условным операторам, определяющим синтаксис в зависимости от броузера, при помощи которого просматривается страница. Одним из таких способов является проверка поддерживаемых объектов. С объектами document.layers и document.all мы уже познакомились. В случае броузеров Netscape Navigator версии 6 и Internet Explorer 5.х следует использовать конструкцию document.getElementById:
spanNode = document.getElementById('myTab');
while (spanNode.hasChildNodes()) spanNode.removeChild(spanNode.last-Child);
var range = document.createRange();
range.selectNodeContents(spanNode);
spanNode.appendChild(range.create-ContextualFragment(output));
Приведенный выше код удовлетворяет DOM Level 1. Функция getElementById() используется для получения ссылки на узел дерева объектов DOM, соответствующий нашему тегу SPAN. С использованием методов hasChildNodes(), removeChild(), lastChild удаляются все дочерние узлы данного тега.
Данное действие можно описать проще: удаляются все объекты (в нашем случае - форма), находящиеся в данный момент внутри тега SPAN.
Затем создается объект Range, который используется для доступа к содержимому тега. После этого сформированную с использованием функции show() форму помещаем в тег.
В завершение статьи я посчитал необходимым представить полный код HTML-документа, дабы избежать недопонимания в процессе "сборки" кода из описанных выше фрагментов.
Внешний вид документа представлен на рис. 5.
<HTML><HEAD>
<SCRIPT language=JavaScript><!--
function update(n) {
var what;
if (document.getElementById) what =
document.getElementById('myForm').-elements;
else if (document.all) what =
document.all['myTab'].myForm.elements;
else if (document.layers) what =
document.layers['myTab'].document.-myForm.-elements;
for (var i=0, j=what.length; i<j; i++) {
var myType = (what[i].type).toLowerCase(), myName = what[i].name;
if (myType == 'radio' && what[i].checked)
document.hiddenForm.elements-[myName].value = what[i].value;
if (myType == 'checkbox')
document.hiddenForm.elements-[myName].value = ((what[i].checked) ?
what[i].value : '');
if (myType == 'password' || myType == 'text' || myType == 'textarea')
document.hiddenForm.elements-[myName].value = what[i].value;
if (myType == 'select-one')
document.hiddenForm.elements-[myName].value =
what[i].options[what[i].selected-Index].text;
}
}
var theCountries = ['Россия','США','Австралия'];
var theAges = ['0 - 17','16 - 21','22 - 30','30 - 45','45 - 60','60+'];
var theSkills = ['Java','JavaScript','VBScript','ASP',-'HTML'];
function show(n) {
var output = '<form name="myForm">';
switch(n) {
case 1:
// Генерация формы для указания контактной иформации
output += '\n<p>Имя:<br>';
output += '\n<input type="text" name="myName" value="' +
document.hiddenForm.myName.value + '" size="30">';
output += '\n<p> Адрес электронной почты:<br>';
output += '\n<input type="text" name="myEmail" value="' +
document.hiddenForm.myEmail.value + '" size="30">';
output += '\n<p> Имя пользователя:<br>';
output += '\n<input type="text" name="myUserid" value="' +
document.hiddenForm.myUserid.value + '" size="30">';
output += '\n<p>Пароль:<br>';
output += '\n<input type="password" name="myPassword" value="' +
document.hiddenForm.myPassword.value +
'" size="30">';
break;
case 2:
// Генерация формы для ввода адреса
output += '\n<p>Адрес1:<br>';
output += '\n<input type="text" name="myAddress1" value="' +
document.hiddenForm.myAddress1.value + '" size="30">';
output += '\n<p>Адрес2:<br>';
output += '\n<input type="text" name="myAddress2" value="' +
document.hiddenForm.myAddress2.value + '" size="30">';
output += '\n<p>Адрес3:<br>';
output += '\n<input type="text" name="myAddress3" value="' +
document.hiddenForm.myAddress3.value + '" size="30">';
output += '\n<p>Страна:';
output += '\n<br><select name="myCountry">';
for (var i=0; i<theCountries.length; i++) {
if (document.hiddenForm.myCountry.value == theCountries[i])
output += '\n<option selected>' + theCountries[i];
else
output += '\n<option>' + theCountries[i];
}
output += '\n<\/select>';
break;
case 3:
// Отображаем переключатели для указания возраста
output += '\n<p>Возраст:';
for (var i=0; i<theAges.length; i++) {
if (document.hiddenForm.myAge.value == theAges[i])
output += '\n<br><input checked type="radio" name="myAge" value="' + theAges[i] + '"> ' + theAges[i];
else
output += '\n<br><input type="radio" name="myAge" value="' + theAges[i] + '"> ' + theAges[i];
}
break;
case 4:
// Поля ввода для выбора навыков
output += '\n<p>Skills:'
for (var i=0; i<theSkills.length; i++) {
if (document.hiddenForm.elements[the-Skills[i]].value == 'on')
output += '\n<br><input checked type="checkbox" name="' +
theSkills[i] + '"> - ' + theSkills[i];
else
output += '\n<br><input type="checkbox" name="' +
theSkills[i] + '"> - ' + theSkills[i];
}
break;
}
output += '\n<\/form>';
if (document.getElementById) {
if (window.HTMLElement) {
spanNode = document.getElementById('myTab');
while (spanNode.hasChildNodes())
spanNode.removeChild(spanNode.last-Child);
var range = document.createRange();
range.selectNodeContents(spanNode);
spanNode.appendChild(range.create-ContextualFragment(output));
}
else {
document.all('myTab').innerHTML = output;
}
}
else if (document.all)
document.all('myTab').innerHTML = output;
else if (document.layers) {
document.layers['myTab'].document.-open();
document.layers['myTab'].document.writeln-(output);
document.layers['myTab'].document.-close();
}
}
//--></SCRIPT>
</HEAD>
<BODY onload="if (document.getElementById || document.all || document.layers) show(tab=1)">
<form name="hiddenForm" method="get" action="processform.php">
<input type="hidden" value="" name="myName">
<input type="hidden" value="" name="myEmail">
<input type="hidden" value="" name="myUserid">
<input type="hidden" value="" name="myPassword">
<input type="hidden" value="" name="myAddress1">
<input type="hidden" value="" name="myAddress2">
<input type="hidden" value="" name="myAddress3">
<input type="hidden" value="0" name="myCountry">
<input type="hidden" value="" name="myAge">
<input type="hidden" value="" name="Java">
<input type="hidden" value="" name="JavaScript">
<input type="hidden" value="" name="VBScript">
<input type="hidden" value="" name="ASP">
<input type="hidden" value="" name="HTML">
<input type="button" value="Имя" onClick="update(tab);show(tab=1)">
<input type="button" value="Адрес" onClick="update(tab);show(tab=2)">
<input type="button" value="Возраст" onClick="update(tab);show(tab=3)">
<input type="button" value="Навыки" onClick="update(tab);show(tab=4)">
<input type="submit" value="Отправить!">
</form>
<SPAN id=myTab style="POSITION: absolute"></SPAN>
</BODY></HTML>
Ну что же, я думаю, в рамках нашего сегодняшнего разговора я рассказал достаточно. Желаю вам удачного кода и успехов в освоении такого мощного инструмента разработки интерактивных HTML-страниц, каким является Dynamic HTML.
Источник: "Компьютер Price", http://www.comprice.ru