分享

用DHTML 實現動態表單

 douli 2005-07-23

Dynamic Forms with DHTML

表单(Form)是所有基于web的应用程序的关键组件。然而尽管它们如此重要,Web开发人员还是经常向用户提供一些难于使用的表单。下面是三个常见的问题:

  • 表单太长。一个看起来无休无止的问题清单肯定会使用户点击后退(back)按键,或者跳到另外的网站。
  • 在很多情况下,一个特定的用户只需要填写部分表单内容。如果您显示那些没有必要的问题,则实际上是加入导致页面混乱的信息,鼓励用户访问别的网页。
  • 表单的输入通常需要按照特定的格式进行。然而把这种信息加入到Web页面上可能会使屏幕看起来混乱,而且没有吸引力。

这里有一个表单实例,即使是最无畏的用户也肯定会被赶走。本文将向您演示如何用少量的JavaScript来解决所有的这些问题,以及如何让您的网站用户得到更好的体验。

选择您希望的表单

我们可以用很多方法来缩短一个长的表单。举例来说,Internal Revenue Service(内部收入服务)就提供了很多简单的和复杂的表单版本;1040-A收入税表单也有一个较为简单的版本,即1040-EZ,提供给那些税务事务比较简单的人使用。

如果一个表单有多个版本,则我们的主要任务就变成如何把用户指向正确的表单。通常情况下,用一组简单的连接就可以了,比如:简单表点电击这里,复杂表单点击这里。还有一种可选的方法:用一个页面来显示多个可选表单,访问者可以通过收音机按钮在可选表单之间进行切换。这种方法对于复杂页面,或者慢速连接用户都特别好,因为装载新页面的时候没有延迟。请看一下这个用类似帧的方法作成的例子,它提供了多个表单。

这个方法使用了动态HTML(Dynamic HTML,即DHTML)技术,跟使用IFRAMES实现同样的任务相比,这个方法有几个好处。首先,DHTML提供了比IFRAMEs支持的更灵活的格式化手段。您可以把图像,边界,字体,和所有您知道的,可以用于HTML和层叠式风格表单(Cascading Style Sheets,即CSS)的技术应用到DHTML对象中,相比之下,IFRAMES几乎没有可以配置的功能。第二,如果有人在填写一个表单时,切换到另一个表单,然后再切换回来,那浏览器有很大的可能性会丢失开始时输入的信息。在基于DHTML的解决方案中,没有这个问题。第三,使用DHTML,您可以做一些比较有技巧的事,象裁剪,以及在页面上移动表单。您可以把IFRAMES和DHTML结合起来实现这些功能,但是首选还是只使用DHTML。

choose_form.html页面中的实现方法相当直接,特别是当您读过Internet Developer(英特网开发者)上的隐藏/显示层这篇文章之后,就更有这种感觉。每一个表单被放在一个DIV中,而每一个DIV和一个收音机按钮相关联。在收音机按钮被选择时改变其相关联的DIV的可视性。您可以看一下这个例子的源代码。下面是对这个例子的简要分析。

首先,每一个表单处于它自己的DIV中。下面是包含表单的DIV在某种程度上的简化版本:

<div id="ez" 
  style="position:absolute;top:100px;left:5px;visibility:hidden;">
<form name="ez_form" method="POST" 
  action="http:///cgi-bin/thanks.cgi">
<table>
<tr><td>Name:</td><td><input type="text"></td><tr>
<tr><td>Income:</td><td><input type="text"></td></tr>
</table>
<input type="submit">
</form>
</div>

这里有一些要点值得一提。首先,DIV有一个id(id="ez"),用于标识您要隐藏或者显示的DIV;其次,DIV有自己的风格信息,这个信息定位了DIV(position:absolute;top:100px;left:5px)并在页面首次装载时将它隐藏(visibility:hidden)。

技巧在于用收音机按钮来显示正确的DIV。下面是一个收音机按钮的代码:

<input type="radio" name="form_type" value="ez"
  onClick="switchDiv(‘ez‘);">Easy Form

点击这个按钮会调用 switchDiv() 函数,这个函数首先查看浏览器是否可以处理DHTML。如果可以的话,就首先调hideAll()函数,用以隐藏所有包含表单的DIV,然后再调用changeObjectVisibility()函数来显示被请求的DIV。

下面是 switchDiv() 函数:

function switchDiv(div_id)
{
  var style_sheet = getStyleObject(div_id);
  if (style_sheet)
  {
    hideAll();
    changeObjectVisibility(div_id, "visible");
  }
  else 
  {
    alert("sorry, this only works in browsers that do Dynamic HTML");
  }
}

首先,switchDiv()尝试用div_id这个id来获取DIV的风格信息。它使用的函数叫getStyleObject(),这个函数在隐藏和显示层这篇文章中有描述。getStyleObject()函数接收一个DIV的id,返回该DIV的风格信息,它负责处理与访问风格表单相关的跨浏览器问题,因此这个的脚本可以工作在Netscape 4.0+和Internet Explorer 4.0+浏览器上。如果页面的访问者使用更早期的浏览器,则getStyleObject()函数会返回假(false)。下面是这个函数的源代码,供您精读:

function getStyleObject(objectId) {
  // checkW3C DOM, then MSIE 4, then NN 4.
  //
  if(document.getElementById && document.getElementById(objectId)) {
	return document.getElementById(objectId).style;
   }
   else if (document.all && document.all(objectId)) {  
	return document.all(objectId).style;
   } 
   else if (document.layers && document.layers[objectId]) { 
	return document.layers[objectId];
   } else {
	return false;
   }
}

回到switchDiv()函数:如果getStyleObject()函数成功返回一个风格表单,则hideAll()和changeObjectVisibility()函数就会被调用。

changeObjectVisibility()函数在隐藏和显示层这篇文章中也有描述。它接收两个参数,一个是即将被改变属性的DIV的id,另一个是该DIV的可视性属性值(可视或者隐藏):

function changeObjectVisibility(objectId, newVisibility) {
    // first get the object‘s stylesheet
    var styleObject = getStyleObject(objectId);

    // then if we find a stylesheet, set its visibility
    // as requested
    //
    if (styleObject) {
	styleObject.visibility = newVisibility;
	return true;
    } else {
	return false;
    }
}

首先,这个函数用getStyleObject()函数来访问DIV风格表单对象。如果该风格表单存在,则根据第二个参数的内容改变DIV的可视性。如果第二个参数的值是hidden(隐藏),DIV就变成隐藏状态;如果这个值为visible(可视),则DIV就变成可视状态。

脚本中的最后一个函数是hideAll():

function hideAll()
{
   changeObjectVisibility("ez","hidden");
   changeObjectVisibility("full","hidden");
   changeObjectVisibility("superduper","hidden");
}

这个函数简单地三次调用changeObjectVisibility(),每次隐藏一个含有表单的DIV。

综合起来说,点击一个收音机按钮会调用switchDiv()函数,它又调用hideAll()来隐藏所有的DIV,然后再调用changeObjectVisibility()函数来显示恰当的DIV;点击另一个收音机按钮就会再次隐藏所有的DIV,以及显示您希望的DIV。

getStyleObject()函数和changeObjectVisibility()函数将会在本文的多个例子中用到。由于它们频繁地被使用,把它们转移到一个文件中是一个好的想法,这样可以被包含到以后的脚本中,如下所示:

<script src="utility.txt"></script>

把用于很多脚本的函数放到一个独立的文档中通常都是一个好主意。这样,如果您希望改变其中一个函数,则只需要改变一个文档,而不必深入到每一个使用这个函数的脚本,并在脚本中进行修改了。

表单向导

您刚才已经看到,显示短一些的表单可以使那些不需要回答所有问题的人们日子好过一些。但是,对于那些的确需要回答很长的问题列表中的每一个问题的人们,又该如何呢?同样地,您也可以让他们愉快一些。

使长的问题列表变得更加“美味”的一个方法是把一个表单变成几个页面,就象软件应用程序中常见的向导程序那样。这种方法对访问者在特定时刻能够看见的问题数目加以限制,减少了用户因为恐惧而跑掉的可能性。同时,有人一旦经过了一个页面,或者回答了两个问题,就很可能填完所有信息。

很多网站把长的表单分成若干小的部分,而把每个部分都提交给运行在web服务器上的一个CGI脚本。绝大部分的电子商务网站把订购的表单至少分成三个表单:一个确认购买,一个收集送货信息,还有一个用来填写付帐信息。在第一个表单被提交之后,表单中的信息就被发送到web服务器上,服务器上的程序就把这些信息存储起来,并显示第二个表单。

这种方法就一个好处,就是逐步存储信息。举例来说,如果您正在做一个调查,则这种方法意味着即使访问者进行到一半就停止表单的填写,您仍然可以得到一些信息。这种方法的缺点是表单的每个部分都要独立地在客户和服务器之间来回传递。如果连接是慢速的,则意味着可能会因为响应缓慢而丢失访问者。这种方法也假定您对web服务器上的脚本引擎有访问权限,并且您知道如何书写服务器端的脚本。

另外一个比较不经常用的方法是用DHTML来显示表单的不同部分。把每个子表单放入她自己的DIV中,在开始的时候显示第一个DIV上的子表单,然后当访问者填完了这一部分内容后,用JavaScript把第一个DIV隐藏起来,然后显示第二个DIV。这种方法把表单分割成若干小块,节省了和服务器来回传递的时间,并且不要求访问服务器端的脚本。您可以在DHTML向导中查看这个例子是如何工作的,然后再看一下这个向导程序的源代码,看看代码中涉及到什么技术。

向导程序的工作机制

在向导程序的代码中有一些有趣的地方。首先,让我们看一下第二个DIV的稍微缩短过的版本:

<div id="part2" style="position:absolute;top:150px;left:50px;visibility:hidden;">
<form name="pet_info">
<table>
<tr><td>Your pet‘s name:</td>
    <td><input name="petname" type="text"></td></tr>

</table> 

<input type="button" value="prev"
  	onClick="switchDiv(‘part2‘, ‘part1‘);">

<input type="button" value="next" 
	onClick="switchIfDone(this.form, ‘part2‘, ‘part3‘);">
</form>
</div>

这里也有一些要点值得一提。和以前一样,DIV有一个id,它使得JavaScript可以根据需要隐藏和显示DIV。这个DIV也有风格信息,用来定位DIV,并在页面首次装载时将之隐藏。

在DIV中存在一个含有一系列问题输入域的表单。请注意,表单有一个名字,每个问题输入域也都有一个名字。脚本程序在后面会根据这些名字从表单的不同部分收集数据。

除了这些问题域之外,在表单的尾部有两个按键。第一个按键使访问者可以回到原来的页面去改变问题的答案。它调用了switchDiv()函数并传入两个参数:即包含访问者正在查阅的表单部分的DIV id,以及包含即将被访问的表单部分的DIV id。第二个按键则使访问者一旦填完当前的表单部分之后可以移动到表单的下一个部分。它调用了switchIfDone()函数,这个函数接收三个参数:表单的属性名称,当前可见的DIV id,以及接下来即将被显示的DIV id。

大部分的工作由switchDiv()和switchIfDone()函数来完成。第一个函数相当直接:

function switchDiv(this_div, next_div)
{
  if (getStyleObject(this_div) && getStyleObject(next_div)) {
    changeObjectVisibility(this_div, "hidden");
    changeObjectVisibility(next_div, "visible");
  }
}

这个函数和先前例子中看到的changeDiv()函数相类似。它用getStyleObject()函数来确认浏览器是否可以处理DHTML。如果可以的话,就调用changeObjectVisibility()函数将当前可见的DIV隐藏,并显示下一个应该被看见的DIV。

switchIfDone()函数负责确定表单是否完全被填充。如果表单已经完成,而用户正在查看表单的最后部分,则应该把所有表单上的所有信息发送到服务器上。如果用户正在查看的并不是向导程序的最后部分,则switchDiv()函数应该把页面移动到下一个表单。

当访问者点击表单上的next按键时,switchIfDone() 函数就会被调用,如下所示:

<input type="button" value="next" 
	onClick="switchIfDone(this.form, ‘part2‘, ‘part3‘);">

第一个参数是一个指针,指向含有按键的表单。该函数将会检查表单是否填写正确,如果正确,就把第二部分隐藏,然后显示第三部分。

下面是判断表单是否填写完成的代码:

  var complete = true;
  for (var loop=0; loop < the_form.elements.length; loop++)
  {
    if (the_form.elements[loop].value == "")
    {
      complete = false;
    }
  }

这个循环遍历了表单中的所有元素,确认这些元素是否都不为空。如果有一个元素为空,则把complete变量设置为假(false)。

一旦上述循环执行完成,函数接下来就运行如下代码:

  if ((complete == true) && (next_div == "finished")) 
  {
    submitTheInfo();
  }

如果表单已经填充完成,且next_div参数被设定为finished(完成),则submitTheInfo()函数就会被调用。为了使这段代码正确地工作,含有最后一部分表单的DIV中的next按键应该如下所示:

<input type="button" value="next" 
	onClick="switchIfDone(this.form, ‘part3‘, ‘finished‘);">

如果第三个参数不是"finished",则执行else-if部分的代码。

else if (complete == true) 
  {
    switchDiv(this_div, next_div);
  }

如果表单完成了,则调用switchDiv()函数,向访问者显示表单向导的下一个部分。如果这个else-if失败了,则表单应该肯定没有完成,因此执行最后一个else:

else {
    alert(‘please complete the form before moving on‘);
  }

脚本中的最后一个函数真正地把信息提交给CGI脚本。我们也可以在这里对访问者提供的信息进行必要的处理:

function submitTheInfo()
{
  var submission_string="";
  for (var form_loop=0; form_loop<document.forms.length; form_loop++) 
  {
    for (var elems=0; elems<document.forms[form_loop].length;elems++)
    {
      if (document.forms[form_loop].elements[elems].name != "")
      {
        submission_string += document.forms[form_loop].name + "_" +
          document.forms[form_loop].elements[elems].name + "=" +
          document.forms[form_loop].elements[elems].value + "\n";
      }
    }
  }
  document.hiddenform.the_text.value = submission_string;

  // the next two lines are written for debugging - 
  // to put the script into action
  // comment out the changeObjectVisibility() line
  // and uncomment the document.hidden.form.submit() line
  //

  // document.hiddenform.submit(); 
  changeObjectVisibility("hiddenstuff","visible");

  document.hiddenform.submit();
}

这个函数遍历页面上所有的表单以及表单上的每一个元素,构造一个包含用户提供的所有信息的字符串。然后把这个字符串关联到一个隐藏表单的textarea构件上。包含隐藏表单的DIV大致如下:

&div id="hiddenstuff" style="position:absolute;top:300;left:5;visibility:hidden;">
<form name="hiddenform" method="POST" action="my_cgi_script">
<textarea name = "the_text" cols=40 rows=20>
</textarea>
</form>
</div>

请注意,这个表单有一个方法和动作,它们告诉浏览器当表单被提交时应该执行哪个脚本。下面这行代码:

  document.hiddenform.the_text.value = submission_string;

把含有表单值的字符串关联到textarea构件中,而下面这行代码:

  document.hiddenform.submit();

则把表单提交给action属性指定的CGI脚本。

正如前面的例子说明的那样,向导程序的核心技术涉及到如何根据访问者的动作来隐藏和显示DIV。这种向导的方法稍微有点复杂,因为在提交给CGI脚本之前,必须从表单中收集信息,但是除此之外,想法是一样的。

信息太多

有些时候,对于一个问题的回答可能分成一整套相关的子问题。如果有人在填写您的公寓租赁表单的时候说他或她没有宠物,则您就不应该问和宠物相关的问题。对于这种情况,您可以提供两个表单,一个给有宠物的租户,另一个给没有宠物的租户。然而,这意味着您需要维护多个彼此相当类似的表单。另外一种解决办法就是用DHTML技术使一部分问题可以根据另外一个问题的答案出现或者消失。请看一下租赁表单,那里含有一个说明这种方法如何工作的实例。

这个脚本通过修改风格表单的显示属性来实现元素的隐藏和显示。如果风格表单的显示属性被设定为none(无),那包含该风格表单的DIV在屏幕上就不占用任何空间(因此是不可见的)。如果显示属性被设定为block(块),则DIV就会出现在页面上。当显示属性从none变成block时,DIV下面的所有元素就会向下移动,以便给DIV的内容腾让空间;而当显示属性从block变为none时,则DIV就消失,其下面的内容就向上移动。请注意,这跟可视属性完全不同。当您使一个隐藏的DIV变为可见时,并不会影响到屏幕上其它内容的位置。如果DIV被定位在页面上其它元素之上,则在显示时,它就会出现在这些元素的上方。

请读一下租赁表单的源代码,看一下发生了什么。当访问者点击其中一个收音机按钮,确认他或她有宠物时,JavaScript就会被触发。按键的代码大致如下:

<input type="radio" name="pet" 
	onClick="hideAll(); changeDiv(‘cat_questions‘,‘block‘);">cat

如果访问者拥有一只猫,则浏览器就调用hideAll()函数来隐藏那些可能已经显示出来的和宠物有关的问题,然后调用changeDiv()函数,传入cat_questions和block作为参数。在页面稍微向下一点的地方,有一个id为cat_questions的DIV,下面是这个DIV的简化版本:

<div id="cat_questions" style="margin-left:30px;display:none">
Does your cat have fleas?
<input type="radio" name="fleas" value="yes">yes
<input type="radio" name="fleas" value="no">no
</div>

请注意风格表单中的display:none属性。这个属性告诉浏览器不要显示这个DIV的内容。changeDiv()函数和changeObjectVisibility()函数相类似:

function changeDiv(the_div,the_change)
{
  var the_style = getStyleObject(the_div);
  if (the_style != false)
  {
    the_style.display = the_change;
  }
}

首先,程序调用getStyleObject()函数来获得您希望显示的DIV的风格表单对象。如果这个风格表单对象存在,则其显示属性就会被改变。the_change变量的值如果是block,就会使DIV显示在页面上;如果是none,则DIV就被移走。这里用的getStyleObject()函数和我们一直用的函数稍微有点不同。遗憾的是,在Netscape 4上不能用JavaScript来改变显示属性,因此如果网页访问者使用的是Netscape 4,则这个版本的getStyleObject()函数返回false(假)。

hideAll()函数对包含宠物信息的DIV逐个调用changeDiv()函数进行处理。

function hideAll()
{
  changeDiv("cat_questions","none");
  changeDiv("dog_questions","none");
  changeDiv("bird_questions","none");
}

以上又是一种用JavaScript改变含有表单元素的DIV的风格属性的方法。这个例子和先前的例子之间的主要差别在于,这个例子切换的是风格表单的显示属性,而不是可视属性。

好的表单

帮助页面访问者以正确的格式进行填充,可以使信息输入更加容易。表单通常会被一些输入的提示弄乱,比如如何格式化日期,如何输入信用卡号,等等。格式助手页面介绍如何帮助您的用户正确输入,而又不至于因为输入的提示而把页面弄乱。

在上面这个实例中发生的一些事情值得一提。首先,点击文本输入框会弹出一个格式化输入的辅助部件,来说明应该如何填充这个元素。您可能可以猜到,这可以通过把相应的部件放在DIV里面,并改变DIV的可视属性来实现。此外,脚本程序还避免了页面的访问者直接在文本域上进行输入,使他们只有通过相应的部件才能进行输入,这可以确保输入的格式正确无误。最后,脚本从输入部件中取出输入信息,将它放置到文本域中。

让我们首先看一个主文本输入框,即访问者输入姓名的文本框:

Name: <input type="text"  name="the_name" 
  onFocus= "hideAll(); 
      showAndFocus(‘nameDiv‘,document.name_form.first_name);"><br>

这个输入域有一个onFocus的事件处理函数。如果有人点击该文本框,就会触发一个焦点事件,运行与这个事件相关联的JavaScript。这时,浏览器就会调用hideAll()函数,把当前可视的部件隐藏起来,然后调用showAndFocus()函数来显示正确的部件:

hideAll()函数对我们来说应该有点面熟:

function hideAll()
{
  changeObjectVisibility("dateDiv","hidden");
  changeObjectVisibility("nameDiv","hidden");
}

这个函数调用我们在其它例子中用过的changeObjectVisibility()函数,把指定的DIV设置为hidden(隐藏)。

showAndFocus()函数负责显示正确的部件,并把光标从用户点击的表单元素移动到该输入部件上。它接收两个参数:DIV的id和即将成为焦点的表单域。具体到这个例子,脚本传入的DIV id是nameDiv,即包含姓名输入部件的DIV;第二个参数是document.name_form.first_name,它是部件中的即将成为光标焦点的文本域。下面是showAndFocus()函数的源代码:

function showAndFocus(div_id, field_to_focus)
{
  var the_div = getStyleObject(div_id);
  if (the_div != false)
  {
    changeObjectVisibility(div_id, "visible");
    field_to_focus.focus();
  }
}

这个函数首先确认是否可以访问DIV的风格信息,使用的函数是我们的老朋友,getStyleObject()。如果风格表单可以被访问,该函数就调用我们的另外一个老朋友,changeObjectVisibility()函数,来把DIV的内容变为可视;然后调用表单元素的focus()方法,以便把光标移动到输入部件上。任何时候,如果有人点击主姓名输入域,则输入部件就会弹出,并且抓住光标焦点。这就是为何我们可以保证主姓名域上的任何信息都是被正确格式化了的原因。从那里获取信息的唯一途径是通过姓名输入部件。

姓名输入部件的代码大致如下:

<div id="nameDiv" style="position:absolute;top:50px;left:100px;
   visibility:hidden;z-index:2;background-color:#CCCCCC">
<form name="name_form">
First name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="button" value="DONE" 
onClick="fillInName(this.form.first_name.value,
this.form.last_name.value);">
</form>
</div>

这是一个含有一个表单的DIV,和我早些时候用的类似。最后一个表单元素是DONE按键,当这个按键被点击时,fillInName()函数就会被调用。fillInName()函数取出部件的输入信息,并将这些信息放到主姓名域中,然后把输入部件隐藏起来:

function fillInName(first_name, last_name)
{
  document.main_form.the_name.value = 
		first_name + " " + last_name;

  changeDiv("nameDiv","hidden");  
}

日期信息的输入部件的工作机制与此类似,请看一下表单助手的源代码,那里可以看到整个脚本代码。

结束语

在这篇文章中,我演示了如何用DHTML技术使表单更加易于填充的四种方法。让用户选择他们需要的表单可以确保用户看到的表单不会比他们实际需要的表单更加复杂。

有意思的是,虽然这些应用看起来各不相同,但是其实现方法却相当类似。事实上,有几个函数被共用到所有的例子代码中。好的函数就象是积木,您可以把它们用到很多不同的应用程序中。请把这里看到的函数带走,试着用到您自己的应用程序中。您就肯定可以毫不费力地完成一些复杂而强大的功能。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多