JavaScript-DOM-和-AJAX-入门指南-三-

108 阅读1小时+

JavaScript DOM 和 AJAX 入门指南(三)

原文:Beginning JavaScript with DOM Scripting and Ajax

协议:CC BY-NC-SA 4.0

六、JavaScript 的常见用法:图像和窗口

如果你阅读了最后 的几章,你现在应该很好地掌握了 JavaScript 知识及其与层叠样式表(CSS)和 HTML 的交互。现在你将了解 JavaScript 在网络上的一些最常见的用法,我们将通过一些例子。在这些例子中,您将看到如何确保 JavaScript 的这些实现独立于页面上的其他脚本工作,我将解释可能会出现什么问题。我还将谈到一些很容易使用但可能不是最安全的选项的功能。

image 注意这一章有很多代码示例,你会被要求在浏览器中打开其中的一些来自己测试功能。如果你还没有去过www.beginningjavascript.com下载这本书的代码示例,现在可能是个好时机。

这里的大部分完整代码示例使用 DOM-3 事件处理。这使得它们比它们的 DOM-1 对等物更复杂一些,但是这也使得它们能够更好地与其他脚本一起工作,并且它们更有可能在未来的浏览器中工作。请耐心听我说,我保证通过反复使用这些方法,你会很快掌握它们的窍门。

开发这些示例时也考虑到了维护和灵活性。这意味着不熟悉 JavaScript 的人在以后可能会更改的所有内容都存储在属性中,并且您可以很容易地让同一文档的几个部分使用脚本的功能。这也增加了一些脚本的复杂性,但这是大多数客户要求的现实生活中的可交付成果。

图像和 JavaScript

图像的动态变化很可能是 JavaScript 的第一个“惊艳”效果。当浏览器还不支持 CSS 时(公平地说,CSS 仍在定义过程中),当用户将鼠标移到图像上或单击图像时,JavaScript 是改变图像的唯一方法。近年来,越来越多传统上通过 JavaScript 实现的图像效果已经被纯 CSS 解决方案所取代,这使得维护变得更加容易。我将在后面讨论这些;现在,让我们看看 JavaScript 能对图像做些什么的基础知识。

图像脚本基础知识

在 JavaScript 中,您可以通过两种方式访问和修改图像:通过 getElementsByTagName()和 getElementById()的 DOM-2 方式,或者通过涉及存储在 document 对象的属性中的 images 集合的旧方式。举个例子,我们来看一个包含照片列表的 HTML 文档:

<ul class="slides">
  <li><img src="pictures/thumbs/cat2.jpg" alt="Lazy Cat"></li>
  <li><img src="pictures/thumbs/dog10.jpg" alt="Dog using the shade"></li>
  <li><img src="pictures/thumbs/dog12.jpg" alt="Squinting Dog"></li>
  <li><img src="pictures/thumbs/dog63.jpg" alt="Dog cooling off in the sand"></li>
  <li><img src="pictures/thumbs/dog7.jpg"  alt="Very flat dog"></li>
  <li><img src="pictures/thumbs/donkeycloseup.jpg" alt="Curious Donkey"></li>
  <li><img src="pictures/thumbs/donkeyeating.jpg" alt="Hay-eating Donkey"></li>
  <li><img src="pictures/thumbs/kittenflat.jpg" alt="Ginger and White Cat"></li>
</ul>

您可以在 JavaScript 中检索所有这些照片,以两种方式对它们进行处理:

// Old DOM
var photosOldDOM=document.images;
// New DOM
var photos=document.getElementsByTagName('img');

这两种方法都会产生一个包含所有图像作为对象的数组。与任何对象一样,您可以读取并操作它们的属性。比方说,您想知道第三个图像的可选文本。您需要做的只是读出对象的 alt 属性:

// Old DOM alt property
var photosOldDOM=document.images;
alert(photosOldDOM[2].alt);
// W3C DOM-2 alt attribute
var photos=document.getElementsByTagName('img');
alert(photos[2].getAttribute('alt'));

图像有几个属性,其中一些是显而易见的。但是还有一些你可能没听说过的:

  • border:HTML 中 border 属性的值
  • 名称:img 标签的名称属性
  • complete:如果图像已完成加载,则该属性为 true(这是只读的,不能更改该属性)
  • height:图像的高度(以像素为单位,以整数形式返回)
  • width:图像的宽度(以像素为单位,以整数形式返回)
  • hspace:图像周围的水平空间
  • vspace:图像周围的垂直空间
  • lowsrc:同名属性中定义的图像预览
  • src:图像的 URL

您可以使用这些属性来动态地访问和更改图像。如果在浏览器中打开示例文档 exampleImageProperties.html,可以读写演示图像的属性,如图图 6-1 所示。

9781430250920_Fig06-01.jpg

图 6-1 。读取和写入图像的属性

image 注意如果图像的尺寸是通过 HTML 的宽度和高度属性定义的,并且你改变了它的来源,你不会自动改变它的尺寸。例如,激活演示中的“设置其他图片”按钮。这可能会导致另一幅图像难看的扭曲,因为浏览器不会以复杂的方式调整图像的大小。

预加载图像

如果您在页面中动态地使用图像来获得翻转或幻灯片效果,您会希望将图像加载到浏览器的内存缓存中,以给访问者一个流畅的体验。你可以用几种方法做到这一点。一种方法是在初始化页面时为每个要预加载的图像创建一个新的图像对象:

kitten = new Image();
kitten.src = 'pictures/kittenflat.jpg';

在“翻转效果”一节中,您将很快看到一个这样的例子:

function simplePreload() {
  var args = simplePreload.arguments;
  document.imageArray = new Array( args.length );
  for(var i = 0; i < args.length; i++ ) {
    document.imageArray[i] = new Image;
    document.imageArray[i].src = args[i];
  }
}

如果您使用想要预加载的图像调用此函数,它将创建一个包含所有图像的新数组,一个接一个地加载它们,例如:

simplePreload('pictures/cat2.jpg', 'pictures/dog10.jpg');

一种不同的、独立于脚本的预加载图像的方式是将它们作为 1×1 像素的图像放在 HTML 的容器元素中,该元素通过 CSS 隐藏。这混合了结构和行为,与任何图像预加载技术有相同的问题:你强迫访问者下载大量他可能不想立即看到的图像。如果您使用预加载器,您可能希望让它们保持可选,让用户决定是否要预加载所有图像。

我将在这里简短地讨论图像预加载,因为关于图像还有很多东西要学。

翻滚特效

当 JavaScript 首次在最常见的用户代理中得到广泛支持时,翻转或悬停效果是绝对的狂热。编写了许多脚本,出现了许多小工具,允许“无需任何编码的即时翻转生成”

翻转效果的想法很简单:你将鼠标悬停在一个图像上,图像会发生变化,这表明这是一个可点击的图像,而不仅仅是视觉效果。图 6-2 显示了翻转效果。

9781430250920_Fig06-02.jpg

图 6-2 。翻转效果意味着当鼠标悬停在元素上时,元素会改变其外观

使用多个图像的翻转

当鼠标悬停在图像上时,可以通过更改图像的 src 属性来创建翻转效果。老式的翻转效果依赖于标签的 name 属性,并使用了 images 集合。像这样的结构在 20 世纪 90 年代的网页中并不少见:

exampleSimpleRollover.html(节选)

HTML

<a href="contact.html"
   onmouseover="rollover('contact', 'but_contact_on.jpg')"
   onmouseout="rollover('contact', 'but_contact.jpg')">
   <img src="but_contact.jpg" name="contact" width="103" height="28" alt="Contact Us" border="0">
</a>

JavaScript

function rollover( img, url ) {
  document.images[img].src=url;
}

翻转的问题是(现在仍然是)第二个映像可能还没有加载,这是适得其反的。这是一个交互元素的事实并不是显而易见的——只有在显示第二个图像时才变得明显。因此,在这种情况下,这会使用户感到困惑,而不是有所帮助。这就是为什么传统的翻转功能(如 Adobe Dreamweaver 附带的功能)将前面介绍的图像对象预加载技术与 name 属性结合使用。

丹尼尔·诺兰在 2003 年提出了一个非常聪明的解决方案,正如在 www.dnolan.com/code/js/rol… imgover 的类添加到您希望具有翻转效果的图像中。

您可以使用 DOM-3 处理程序轻松复制相同的功能。首先,您需要一个 HTML 文档,其中包含分配了正确类别的图像:

exampleAutomatedRollover.html(节选)

<ul>
  <li>
    <a href="option1.html">
      <img src="but_1.jpg" class="roll" alt="option one">Option 1
    </a>
  </li>
  <li>
    <a href="option2.html">
      <img src="but_2.jpg" class="roll" alt="option two"> Option 2
    </a>
  </li>
  [... code snipped ...]
</ul>

然后你计划你的剧本。脚本的主要对象将被称为 ro,用于翻转。因为您想让未来的维护者尽可能地简单,所以您保留了主对象属性中可能发生变化的所有细节。

在这个脚本中,这个类定义了哪个图像应该获得翻转状态以及鼠标经过图像的后缀。在这种情况下,您将分别使用“roll”和“_on”。您将需要两个方法:一个初始化效果,一个做翻转。此外,您将需要一个数组来存储预加载的图像。所有这些共同构成了翻转脚本的框架:

自动调速器. js(骨架)

ro = {
  rollClass : 'roll',
  overSrcAddOn : '_on',
  preLoads : [],
  init : function(){},
  roll : function( e ){}
}
DOMhelp.addEvent( window, 'load', ro.init, false );

让我们开始充实骨架吧。首先是属性和 init 方法。在其中,您预定义了一个名为 oversrc 的变量,并将文档的所有图像存储在一个名为 imgs 的数组中。你循环遍历这些图片,跳过那些没有合适的 CSS 类的图片:

自动调速器. js (excerpt)

ro = {
  rollClass : 'roll',
  overSrcAddOn : '_on',
  preLoads : [],
  init : function() {
    var oversrc;
    var imgs = document.images;
    for( var i = 0; i < imgs.length; i++ ) {
      if( !DOMhelp.cssjs('check', imgs[i], ro.rollClass ) ) {
        continue;
      }

如果图像附加了正确的 CSS 类,则读取其 source 属性,用 overSrcAddOn 属性中定义的后缀替换其中的句号,后跟一个句号,并将结果存储在 oversrc 变量中:

automatedRollover.js(续)

oversrc = imgs[i].src.toString().replace('. ',ro.overSrcAddOn + '. ');

image 注意例如,文档中的第一个图像具有 src but_1.jpg。此处定义了后缀属性的 oversrc 的值将是 but_1_on.jpg

然后创建一个新的 image 对象,并将其存储为 preLoads 数组的一个新项。将新图像的 src 属性设置为 oversrc。使用 DOMhelp 库中的 addEvent()为 mouseover 和 mouseout 添加一个指向 roll 方法的事件处理程序。

automatedRollover.js(续)

   ro.preLoads[i] = new Image();
   ro.preLoads[i].src = oversrc;
   DOMhelp.addEvent( imgs[i], 'mouseover', ro.roll, false );
   DOMhelp.addEvent( imgs[i], 'mouseout', ro.roll, false );
  }
},

roll 方法通过 getTarget(e)检索发生事件的图像,并将其 src 属性存储在一个名为 s 的变量中,然后通过读取事件类型来测试发生了哪个事件。如果事件类型是 mouseover,您可以将文件名中的句号替换为 add-on 后跟一个句号,如果事件是 mouseout,则反之亦然。向窗口添加一个事件处理程序,当窗口完成加载时,它调用 ro.init():

automatedRollover.js(续)

  roll : function( e ) {
    var t = DOMhelp.getTarget( e );
    var s = t.src;
    if( e.type == 'mouseover' ) {
      t.src = s.replace('.', ro.overSrcAddOn + '. ' );
    }
    if( e.type == 'mouseout' ) {
      t.src = s.replace( ro.overSrcAddOn + '. ', '. ' );
    }
  }
}
DOMhelp.addEvent( window, 'load', ro.init, false );

演示页面的结果,如图 6-3 所示,突出显示了当用户悬停在原始图像上时已经加载到浏览器缓存中的图像。

9781430250920_Fig06-03.jpg

图 6-3 。预载和自动翻转

尽管您可以尝试使用巧妙的脚本来预加载图像,但它可能并不总是有效。用户的浏览器缓存设置或其连接中的特殊设置可能会使其无法在不真正将图像添加到文档的情况下偷偷预加载某些内容。因此,您可能会发现更安全的选择是为翻转效果使用单个图像。

使用单一图像的翻转效果

当 CSS 设计者开始探索:hover 伪选择器,不仅仅是改变链接的下划线时,CSS 专用翻转就诞生了。这基本上意味着你给链接和链接的悬停状态分配不同的背景图像。

同样的问题也发生了——图像必须在显示之前加载,这使得翻转效果闪烁或者根本不发生。解决方案是为两种状态拍摄一张单独的图像,并使用背景位置属性来改变图像的位置,如图 6-4 所示。

9781430250920_Fig06-04.jpg

图 6-4 。背景位置和 CSS 的翻转效果

您可以通过在浏览器中打开 exampleCSSonlyRollover.html 来查看效果。所讨论的 CSS 将链接限制在某个大小,并通过将处于悬停状态的背景图像向左移动(通过图像宽度一半的负背景位置值)来实现翻转效果:

exampleCSSonlyRollover.html(节选)

#nav a{
  width:103px;
  padding-top:6px;
  height:22px;
  background:url(doublebutton.jpg) top left no-repeat #ccc;
}
#nav a:hover{
  background-position:-103px 0;
}

在 JavaScript 中也可以这样做;然而,让我们更有创造性,做一些 CSS 不能做的事情。

父元素上的翻转效果

让我们用一个 HTML 列表,通过添加一个漂亮的背景图片,把它变成一个时髦的导航栏,然后当鼠标悬停在链接上时,让链接改变背景图片。你首先需要的是一张背景图片,上面有背景的所有状态,如图图 6-5 所示。

9781430250920_Fig06-05.jpg

图 6-5 。导航背景与所有状态(调整大小)

导航栏的 HTML 是一个链接列表。因为基本的网站可用性指南强烈建议永远不要链接当前页面,所以当前链接被替换为一个标签:

exampleParentRollover.html(节选)

<ul id="nav">
  <li><a href="index.html">Home</a></li>
  <li><a href="documentation.html">Documentation</a></li>
  <li><strong>Products</strong></li>
  <li><a href="contact.html">Contact Us</a></li>
</ul>

然而,因为这个导航可能是多级导航菜单中的第一级,高亮可能不是一个强元素,而是列表项上的一个类:

<ul id="nav">
  <li><a href="index.html">Home</a></li>
  <li><a href="documentation.html">Documentation</a></li>
  <li class="current"><a href="products.html">Products</a></li>
  <li><a href="contact.html">Contact Us</a></li>
</ul>

两种情况都必须考虑。解释演示页面中的 CSS 不是本书的目的;可以说,您用 ID nav 固定了列表的维度,将其向左浮动,并浮动其中的所有列表元素。

相反,让我们直接开始计划剧本。您需要为主对象定义几个属性(称为 pr 代表父翻转)、导航列表的 ID、导航的高度(也是每个图像的高度,并且是背景位置所必需的),以及可能用来突出显示当前部分而不是一个<强>标签的可选类:

parentRollover.js(节选)

pr = {
  navId : 'nav',
  navHeight : 50,
  currentLink : 'current',

您从一个初始化方法开始,该方法检查 DOM 支持,以及具有正确 ID 的必要列表是否可用:

parentRollover.js(续)

init : function() {
  if( !document.getElementById || !document.createTextNode ) {
    return;
  }
  pr.nav = document.getElementById( pr.navId );
  if( !pr.nav ){ return; }

下一个任务是遍历这个列表中包含的所有列表项,并检查该项中是否有强元素或者该项是否有“当前”类。如果任一情况为真,脚本应该将循环的计数器存储在主对象的当前属性中。该属性将在 rollover 方法中使用,以将背景重置为原始状态:

parentRollover.js(续)

var lis = document.getElementsByTagName('li');

for(var i = 0; i < lis.length; i++)
{
  if( lis[i].getElementsByTagName('strong').length > 0 || DOMhelp.cssjs('check', lis[i], pr.currentLink) ) {
    pr.current = i;
  }

每个列表项都获得一个名为 index 的新属性,该属性在整个列表数组中包含它的计数器值。使用此属性是一种技巧,可以避免您必须循环遍历所有列表项,并将它们与事件侦听器方法中的目标进行比较。

您分配了两个指向 roll()方法的事件处理程序:一个是当鼠标在列表项上时,另一个是当鼠标离开列表项时。

parentRollover.js(续)

   lis[i].index = i;
   DOMhelp.addEvent( lis[i], 'mouseover', pr.roll, false );
   DOMhelp.addEvent( lis[i], 'mouseout', pr.roll, false );
  }
},

翻转方法始于预定义一个名为 pos 的变量,该变量后来成为显示正确图像所需的偏移值。然后,它调用 getTarget()来确定哪个元素被滚动,并将目标的节点名与 LI 进行比较。这是一种安全措施:尽管您将事件处理程序分配给了 LI,但浏览器实际上可能会将链接作为事件目标发送。对此的一种解释可能是,链接是一种交互式页面元素,而 LI 不是,浏览器的呈现引擎认为链接更重要。您不会知道,但是您应该知道这样一个事实,一些用户代理将链接而不是列表元素视为事件目标。

parentRollover.js(续)

roll : function( e ) {
  var pos;
  var t = DOMhelp.getTarget(e);
  while(t.nodeName.toLowerCase() != 'li' && t.nodeName.toLowerCase() != 'body') {
    t = t.parentNode;
  }

然后定义显示正确背景图像所需的位置。这个位置或者是列表项的索引值,或者是存储的当前属性乘以每个图像的高度。

这两者中的哪一个被应用取决于用户是否将鼠标悬停在列表项上——这可以通过将事件类型与 mouse over 进行比较来找到。相应地设置导航背景位置的样式,然后在页面完成加载后调用 init()方法:

parentRollover.js(节选)

    pos = e.type == 'mouseover' ? t.index : pr.current;
    pos = pos * pr.navHeight;
    pr.nav.style.backgroundPosition = '0 -' + pos + 'px';
  }
}
DOMhelp.addEvent( window, 'load', pr.init, false );

当你在浏览器中打开 exampleParentRollover.html 时,你可以看到滚动导航的不同链接会显示不同的背景图像,如图 6-6 所示。

9781430250920_Fig06-06.jpg

图 6-6 。不同翻转状态下的导航

这是对影响父元素的翻转问题的编程解决方案。但是,它有一个问题:如果菜单项的顺序改变,代码的维护者也必须相应地改变图像。这不是一个非常灵活的解决方案,这就是为什么您最好动态地将类分配给导航列表来定位背景图像。

对脚本的必要更改会影响属性和 roll()方法;初始化保持不变。除了 currentLink 和 navId 属性之外,还需要一个类名来添加到导航列表中。这个新的性质可以称为 dynamicLink。

在 roll()方法中,再次检查触发该方法的事件是否是鼠标悬停,并相应地添加或移除新的动态类。这个动态分配和命名的类由 dynamicLink 属性值和当前索引加一组成(因为对于人类来说,拥有一个名为 item1 class 而不是 item0 的第一个类更容易):

parentCSSrollover.html 使用的 parentCSSrollover.js(略)

pr = {
  navId : 'nav',
  currentLink : 'current',
  dynamicLink : 'item',
  init : function() {
   // [... same as in parentRollover.js ...]
  },
  Roll : function( e ) {
    // [... same as in parentRollover.js ...]
    var action = e.type == 'mouseover' ? 'add' : 'remove';
    DOMhelp.cssjs( action, pr.nav, pr.dynamicLink + ( t.index + 1 ) );
  }
}
DOMhelp.addEvent( window, 'load', pr.init, false );

这样,你就允许 CSS 设计者在 CSS: 中为翻转导航定义不同的状态

parentCSSrollover.html 使用的 parentCSSrollover.css(节选)

#nav.item1{
  background-position:0 0;
}
#nav.item2{
  background-position:0 -50px;
}
#nav.item3{
  background-position:0 -100px;
}
#nav.item4{
  background-position:0 -150px;
}

这也为 CSS 设计者提供了另一个设计导航的钩子:dynamic 类可以用来定义当前的翻转,或者突出显示链接本身的状态,这与不同的项目是不同的。

幻灯片放映

幻灯片是嵌入在页面中的小图像,带有上一页和下一页按钮,有时它们甚至会在一定时间后自动改变图像。它们用于说明文字或提供产品的不同视图。

我们可以区分两种类型的幻灯片放映:在同一文档中包含所有图像的嵌入式幻灯片放映,以及在需要时加载图像的动态幻灯片放映。

嵌入式幻灯片放映

将幻灯片添加到页面的最简单的方法可能是将所有图像作为一个列表添加。然后,您可以使用 JavaScript 通过隐藏和显示带有嵌入图像的不同列表项,将该列表转换成幻灯片。演示文档 examplePhotoListInlineSlideShow.html 正是这样做的,如图 6-7 所示。

9781430250920_Fig06-07.jpg

图 6-7 。带有 JavaScript 的嵌入式幻灯片

底层 HTML 是一个无序列表,所有图像都是列表项。请注意,这也允许您为每个图像设置适当的替代文本:

examplePhotoListInlineSlideShow.html(节选)

<ul class="slides">
  <li>
     <img src="pictures/thumbs/cat2.jpg" alt="Lazy Cat">
  </li>
  <li>
    <img src="pictures/thumbs/dog10.jpg" alt="Dog using the shade">
  </li>
  <li>
    <img src="pictures/thumbs/dog12.jpg" alt="Squinting Dog">
  </li>
  <li>
    <img src="pictures/thumbs/dog63.jpg" alt="Dog cooling off in the sand">
  </li>
  <li>
    <img src="pictures/thumbs/dog7.jpg"  alt="Very flat dog">
  </li>
  <li>
    <img src="pictures/thumbs/donkeyeating.jpg" alt="Hay-eating Donkey">
  </li>
  <li>
    <img src="pictures/thumbs/kittenflat.jpg" alt="Ginger and White Cat">
  </li>
</ul>

所有未来的维护者所要做的就是改变图片的顺序或添加或删除图片来改变 HTML。根本不需要修改 JavaScript。只要你提供一个合适的样式表,没有 JavaScript 的访问者将得到如图图 6-8 所示的所有图像。没有样式表的用户将得到一个带有图像缩略图的列表。

9781430250920_Fig06-08.jpg

图 6-8 。没有 JavaScript 的嵌入式幻灯片放映

在文档中嵌入所有图像的一个效果是,当访问者加载页面时,它们都将被加载。这可能是一件好事,也可能是一件坏事,取决于访问者的连接速度。稍后,我将向您展示一个仅当用户单击较小的图像时才加载较大图像的示例。

让我们看看将这个列表转换成幻灯片的脚本。您将使用在前几章中开发的 DOMhelp 库来解决浏览器问题,并稍微缩短代码。

和往常一样,首先要做的是计划你的剧本。在这种情况下,你应该给 CSS 设计者和 HTML 开发者几个类作为钩子来触发功能或者定义外观和感觉:

  • 一个类,指示列表应该转换为幻灯片放映
  • 定义动态幻灯片列表外观的类
  • 显示先前隐藏的元素的类
  • 定义图像计数器外观的类(例如,图像 1/3)
  • 一个隐藏在特定播放状态下不应该出现的元素的类

您还应该允许维护人员更改前后链接的外观和内容以及图像计数器的文本内容。

至于方法,您所需要的(除了 DOMhelp 中包含的 helper 方法之外)就是一个全局初始化方法、一个初始化每个幻灯片放映的方法和一个放映幻灯片的方法。所有这些共同构成了脚本的框架:

photolistinlineslides . js(skeleton)

inlineSlides = {

  // CSS classes
  slideClass : 'slides',
  dynamicSlideClass : 'dynslides',
  showClass : 'show',
  slideCounterClass : 'slidecounter',
  hideLinkClass : 'hide',

  // Labels
  // Forward and backward links, you can use any HTML here
  forwardsLabel : '<img src="control_fastforward_blue.png" lt="next" >',
  backwardsLabel : '<img src="control_rewind_blue.png"alt="previous" >',
  // Counter text, # will be replaced by the current image count
  // and % by the number of all pictures
  counterLabel : '# of %',

  init : function() {},
  initSlideShow : function( o ) {},
  showSlide : function( e ) {}
}
DOMhelp.addEvent( window, 'load', inlineSlides.init, false );

image 注意在前面的代码中,您在向前和向后链接的标签中提供了一个基于 HTML 的选项。这使得幻灯片的样式更加灵活,因为维护者可以添加自己的 HTML(比如图像)。此外,如果您想让维护人员像更改计数器一样更改动态文本,那么使用#和%这样的占位符并解释它们将被替换是有益的。

让我们一步一步地检查脚本中的方法。首先是全局初始化方法 init():

  1. 测试 DOM 支持。
  2. 如果测试成功,遍历文档的所有 ul 元素。
  3. 对于每个 UL,检查它是否具有将其定义为幻灯片放映的类(存储在 slideClass 属性中),如果没有该类,则跳过该函数执行的其余步骤。(使用“继续”来完成此操作。)
  4. 如果当前的 UL 要变成幻灯片,你用定义动态幻灯片的类替换定义它为幻灯片的类;向列表中添加一个名为 currentSlide 的新属性,并使用列表作为参数调用 initSlideShow 方法。

photoListInlineSlides.js(节选)

init : function() {
  if( !document.getElementById  || !document.createTextNode ) {
    return;
  }
  var uls = document.getElementsByTagName('ul');
  for( var i = 0; i < uls.length; i++ ) {
    if( !DOMhelp.cssjs('check', uls[i], nlineSlides.slideClass ) ) {
     continue;
    }
    DOMhelp.cssjs('swap', uls[i],inlineSlides.slideClass, nlineSlides.dynamicSlideClass );
    uls[i].currentSlide = 0;
    inlineSlides.initSlideShow( uls[i] );
  }
},

使用这些技巧,您可以省去大量的循环和检查工作。首先,仅在 JavaScript 可用时用另一个类替换该类,这允许您隐藏 CSS 中的所有列表项,而不是在 initSlideShow()方法中遍历它们:

photoListInlineSlides.css(节选)

.dynslides li{
  display:none;
  margin:0;
  padding:5px;
}

其他动态分配的 CSS 类是 hide 类(显示第一个图像时删除向后链接,显示最后一个图像时删除向前链接)和 show 类(覆盖使用。动态幻灯片选择器。演示中所有其他的 CSS 选择器和属性都是纯粹装饰性的。

photoListInlineSlides.css(节选)

.dynslides .hide{
  visibility:hidden;
}
.dynslides li.show{
  display:block;
}

通过将当前可见图像存储在列表的属性中,您不需要遍历所有图像并在显示当前图像之前隐藏它们。相反,您需要做的只是确定必须显示哪一个,读取父列表元素的属性,并隐藏存储在该属性中的前一个图像。

然后将属性重置为新图像,下次显示图像时,循环重新开始。您可以将当前图像存储在主 inlineSlides 对象的属性中,但是将它存储在 list 的属性中意味着您允许在同一页面上播放多个幻灯片。

initSlideShow()方法获取每个幻灯片列表作为一个名为 lst 的参数。首先,定义将与 var 关键字一起使用的变量,以确保它们不会覆盖同名的全局变量。然后创建一个新的段落元素来存放前向和后向链接以及图像计数器,并将其直接插入到列表之后(使用 lst.nextSibling):

photoListInlineSlides.js(续)

initSlideShow : function(lst ) {
  var p, temp, count;
  p = document.createElement('p');
  DOMhelp.cssjs('add', p, inlineSlides.slideCounterClass );
  lst.parentNode.insertBefore( p, lst.nextSibling );

接下来,通过 DOMhelp 的 createLink 方法创建向后链接,并使用 innerHTML 添加适当的标签。添加一个事件处理程序来调用 showSlide 方法,通过应用适当的 CSS 类隐藏链接,并将链接添加到新创建的段落中。您将把链接存储在名为 rew 的列表属性中,以便以后更容易找到它:

photoListInlineSlides.js(续)

lst.rew = DOMhelp.createLink('#', ' ' );
lst.rew.innerHTML = inlineSlides.backwardsLabel;
DOMhelp.addEvent(lst.rew, 'click', inlineSlides.showSlide, false );
DOMhelp.cssjs('add', lst.rew,inlineSlides.hideLinkClass );
p.appendChild(lst.rew );

接下来是一个充当图像计数器的新 SPAN 元素。获取主对象的 counterLabel 属性,用当前列表的 currentSlide 属性值替换#字符并加 1(因为人类从 1 开始计数,而不是像计算机那样从 0 开始计数)。将%字符替换为列表中 LI 元素的数量,并将结果字符串作为新的文本节点添加到 SPAN,然后将其作为新的子节点添加到段落:

photoListInlineSlides.js(续)

lst.count = document.createElement('span');
temp = inlineSlides.counterLabel.replace( /#/, lst.currentSlide + 1 );
temp = temp.replace( /%/, lst.getElementsByTagName('li').length );
lst.count.appendChild( document.createTextNode( temp ) );
p.appendChild(lst.count );

image 注意你把计数器的跨度存储在链表的一个属性中,叫做 count。这是纯粹的懒惰,因为它使您不必在以后通过 getElementsByTagName(' span ')[0]来访问它。这也使得脚本不太可能被维护者破坏,维护者可能会在稍后阶段在列表项中添加其他跨度。

添加前向链接与添加后向链接类似,只是 forwardsLabel 属性用作内容,一个名为 fwd 的新属性用作快捷方式。

photoListInlineSlides.js(续)

lst.fwd = DOMhelp.createLink('#', ' ' );
lst.fwd.innerHTML = inlineSlides.forwardsLabel;
DOMhelp.addEvent(lst.fwd, 'click', inlineSlides.showSlide, false );
p.appendChild(lst.fwd );

该方法以获取对应于 currentSlide 属性的列表项并向其中添加 show 类结束。您可以使用 o.firstChild 来代替,但是将来的维护者可能希望最初显示与第一张照片不同的照片:

photoListInlineSlides.js(续)

  temp = lst.getElementsByTagName('li')[lst.currentSlide];
  DOMhelp.cssjs('add', temp, inlineSlides.showClass );
},

showSlide()方法定义了一个名为 action 的变量,并通过 getTarget(e)获取事件目标。因为您不知道维护者是否在链接标签中使用了图像,所以您需要通过测试目标 parentNode 的 nodeName 是否为 a 来找到链接。这也抵消了 Safari 旧版本中的错误,即发送链接中包含的文本作为目标,而不是链接本身。然后,该方法通过读取目标 parentNode 的 closestSibling()来获取触发事件的列表。

photoListInlineSlides.js(续)

showSlide : function( e ) {
  var action;
  var t = DOMhelp.getTarget( e );
  while( t.nodeName.toLowerCase() != 'a' && t.nodeName.toLowerCase() != 'body' ) {
    t=t.parentNode;
  }
  var parentList = DOMhelp.closestSibling( t.parentNode, -1 );

image 注意访问者点击链接的内容可以前进或后退一个图像。事件目标可以是图像(如本例所示)或文本——或者该脚本的维护者放入 forwardsLabel 和 backwardsLabel 属性中的任何东西。因此——因为 Safari 将链接中包含的文本作为目标而不是链接本身发送——您需要检查节点的名称,并将其与 A 进行比较。然后,您获取此 A 的父节点——即新创建的段落——并获取它的前一个兄弟节点,即包含图像的 UL。

接下来,您需要从有问题的列表中找到 currentSlide 属性,并通过检查列表项数组的 length 属性找到图像的总数。通过删除 show 类隐藏以前显示的图像:

photoListInlineSlides.js(续)

var count = parentList.currentSlide;
var photoCount = parentList.getElementsByTagName('li').length - 1;
var photo = parentList.getElementsByTagName('li')[count];
DOMhelp.cssjs('remove', photo, inlineSlides.showClass );

通过比较目标和列表的 fwd 属性来确定被激活的链路是否是前向链路,然后相应地增加或减少计数器。

如果计数器大于 0,则从反向链接中删除隐藏类;否则,添加这个类,有效地隐藏或显示链接。同样的逻辑也适用于前向链接,尽管这次的比较标准是计数器小于列表项的总数。这可以防止在第一张幻灯片上显示向后链接,在最后一张幻灯片上显示向前链接。

photoListInlineSlides.js(续)

count = ( t == parentList.fwd ) ? count+1 : count-1;
action = ( count > 0 ) ? 'remove' : 'add' ;
DOMhelp.cssjs( action, parentList.rew,inlineSlides.hideLinkClass );
action = ( count < photoCount ) ? 'remove' : 'add';
DOMhelp.cssjs( action, parentList.fwd, nlineSlides.hideLinkClass);

它负责链接;现在你需要增加计数器显示。因为计数器存储为列表的一个属性,所以很容易读取该属性的第一个子节点——即 SPAN 中的文本。然后,您可以使用 String 对象的 replace()方法用新的图像编号替换第一个数字条目(这里通过正则表达式),新的图像编号是 count+1——同样,因为人类从 1 开始计数,而不是从 0 开始计数。接下来,重置 currentSlide 属性,获取新照片(记得您更改了 count),并通过添加 show 类显示当前照片。剩下要做的就是在窗口加载后启动 init()方法:

photoListInlineSlides.js(节选)

    photo = parentList.getElementsByTagName('li')[count];
    var counterText = parentList.count.firstChild
    counterText.nodeValue = counterText.nodeValue.replace( /\d/, count + 1 );
    parentList.currentSlide = count;
    photo = parentList.getElementsByTagName('li')[count];
    DOMhelp.cssjs('add', photo, inlineSlides.showClass );
    DOMhelp.cancelClick( e );
  }
}
DOMhelp.addEvent( window, 'load', inlineSlides.init, false );

然而,你还没有完全完成。如果你在旧版本的 Safari 中尝试幻灯片放映,你会意识到向前和向后链接确实被隐藏了,但它们仍然是可点击的,并且当你试图到达不在那里的图像时会导致错误。

image 注意这是动态 web 开发中的一个常见错误——明显隐藏东西并不一定会让它们对所有用户都消失。想想盲人或文本浏览器(如 Lynx)的用户。此外,还要考虑浏览器的漏洞和奇怪之处。

防止这个问题是相当容易的:你需要修改的只是 showSlide()方法,这样当被点击的目标被分配了 hide CSS 类时什么也不做。在解决这个问题时,您还可以添加 Safari 修复来取消新生成的链接的默认操作。演示 examplePhotoListInlineSlideShowSafariFix.html 包含了这些变化:

照片列表中的 slide farifix . js

inlineSlides = {

  // CSS classes
  slideClass : 'slides',
  dynamicSlideClass : 'dynslides',
  showClass : 'show',
  slideCounterClass : 'slidecounter',
  hideLinkClass : 'hide',
  // Labels
  // Forward and backward links, you can use any HTML here
  forwardsLabel : '<img src="control_fastforward_blue.png" alt="next"> ',
  backwardsLabel : '<img src="control_rewind_blue.png" alt="previous">',
  // Counter text, # will be replaced by the current image count
  // and % by the number of all pictures
  counterLabel : '# of %',

  init : function() {
    if( !document.getElementById || !document.createTextNode ) {
      return;
    }
    var uls = document.getElementsByTagName('ul');
    for( var i = 0; i < uls.length; i++ ) {
      if( !DOMhelp.cssjs('check', uls[i],inlineSlides.slideClass ) ) {
       continue;
      }
      DOMhelp.cssjs('swap', uls[i], inlineSlides.slideClass, inlineSlides.dynamicSlideClass );
      uls[i].currentSlide = 0;
      inlineSlides.initSlideShow( uls[i] );
    }
  },
  initSlideShow : function(lst ) {
    var p, temp, count;
    p = document.createElement('p');
    DOMhelp.cssjs('add', p, inlineSlides.slideCounterClass );
    lst.parentNode.insertBefore( p, lst.nextSibling );
    lst.rew = DOMhelp.createLink('#', ' ' );
    lst.rew.innerHTML = inlineSlides.backwardsLabel;
    DOMhelp.addEvent(lst.rew, 'click', inlineSlides.showSlide, false );
    DOMhelp.cssjs('add', lst.rew, inlineSlides.hideLinkClass );
    p.appendChild(lst.rew );
    lst.count = document.createElement('span');
    temp = inlineSlides.counterLabel._
    replace( /#/, lst.currentSlide + 1 );
    temp = temp.replace( /%/, o.getElementsByTagName('li').length );
    lst.count.appendChild( document.createTextNode( temp ) );
    p.appendChild(lst.count );
    lst.fwd=DOMhelp.createLink('#', ' ' );
    lst.fwd.innerHTML = inlineSlides.forwardsLabel;
    DOMhelp.addEvent(lst.fwd, 'click', inlineSlides.showSlide, false );
    p.appendChild(lst.fwd );
    temp = lst.getElementsByTagName('li')[ lst.currentSlide];
    DOMhelp.cssjs('add', temp,inlineSlides.showClass );
    lst.fwd.onclick = DOMhelp.safariClickFix;
    lst.rew.onclick = DOMhelp.safariClickFix;
  },
  showSlide : function( e ) {
    var action;
    var t = DOMhelp.getTarget( e );
    while( t.nodeName.toLowerCase() != 'a && t.nodeName.toLowerCase() != 'body' ) {
      t = t.parentNode;
    }
    if( DOMhelp.cssjs('check', t,_inlineSlides.hideLinkClass ) ){
     return;
    }
    var parentList = DOMhelp.closestSibling( t.parentNode, -1 );
    var count = parentList.currentSlide;
    var photoCount = parentList.getElementsByTagName('li').length-1;
    var photo = parentList.getElementsByTagName('li' )[count];
    DOMhelp.cssjs('remove', photo, inlineSlides.showClass );
    count = ( t == parentList.fwd ) ? count + 1 : count - 1;
    action = ( count > 0 ) ? 'remove' : 'add' ;
    DOMhelp.cssjs( action, parentList.rew, inlineSlides.hideLinkClass );
    action = ( count < photoCount ) ? 'remove' : 'add';
    DOMhelp.cssjs( action, parentList.fwd,inlineSlides.hideLinkClass );
    photo = parentList.getElementsByTagName('li')[count];
    var counterText = parentList.count.firstChild
    counterText.nodeValue = counterText.nodeValue.replace( /\d/, count + 1 );
    parentList.currentSlide = count;
    DOMhelp.cssjs('add', photo, inlineSlides.showClass );
    DOMhelp.cancelClick( e );
  }
}
DOMhelp.addEvent( window, 'load', inlineSlides.init, false );

将嵌入的图像列表转换成幻灯片是一种效果,在非 JavaScript 用户代理上效果很好,尽管它不是真正的图像操作,甚至不是动态的。JavaScript 的真正强大之处在于避免页面重载,并在同一文档中显示更大的图像,而不仅仅是在浏览器中显示。让我们来看一些例子。

动态幻灯片放映

让我们再看一个 HTML 列表,把它变成一个动态幻灯片的例子。从 HTML 开始—这次是包含链接到大图的缩略图的列表:

exampleMiniSlides.html

<ul class="minislides">
<li>
  <a href="pictures/thumbs/cat2.jpg">
     <img src="pictures/minithumbs/cat2.jpg" alt="Lazy Cat">
  </a>
</li>
<li>
  <a href="pictures/thumbs/dog63.jpg">
     <img src="pictures/minithumbs/dog63.jpg" alt="Dog cooling off in the sand"></a>
  </li>
  <li>
    <a href="pictures/thumbs/dog7.jpg">
      <img src="pictures/minithumbs/dog7.jpg"  alt="Very flat dog">
    </a>
  </li>
  <li>
    <a href="pictures/thumbs/kittenflat.jpg">
      <img src="pictures/minithumbs/kittenflat.jpg" alt="Ginger and White Cat">
    </a>
  </li>
</ul>

如果您在启用了 JavaScript 的浏览器中打开示例,您会得到一个小缩略图列表和一个大图像。点击缩略图会用缩略图指向的图像替换大图,如图图 6-9 所示。

9781430250920_Fig06-09.jpg

图 6-9 。带有小预览图像(缩略图)的幻灯片放映

没有 JavaScript 的访问者只会得到一行链接到更大图片的图片,如图 6-10 所示。

9781430250920_Fig06-10.jpg

图 6-10 。没有 JavaScript 的小预览图片幻灯片

同样,让我们来规划脚本的框架:定义一个类来识别哪些列表将变成幻灯片,定义一个类来赋予包含大照片的列表项,定义替换文本来添加到大照片中。

方法和上次一样:一个全局初始化方法,一个初始化每个幻灯片放映的方法,一个显示当前照片的方法。

miniSlides.js (skeleton)

minislides = {
  // CSS classes
  triggerClass : 'minislides',
  largeImgClass : 'photo',
  // Text added to the title attribute of the big picture
  alternativeText : 'large view',

  init : function(){  },
  initShow : function( o ){ },
  showPic : function( e ){  }
}
DOMhelp.addEvent( window, 'load', minislides.init, false );

幻灯片的 CSS 非常简单:

miniSlides.css(节选)

.minislides, .minislides * {
  margin:0;
  padding:0;
  list-style:none;
  border:none;
}
.minislides{
  clear:both;
  margin:10px 0;
  background:#333;
}
.minislides,.minislides li{
  float:left;
}
.minislides li img{
  display:block;
}
.minislides li{
  padding:1px;
}
.minislides li.photo{
  clear:both;
  padding-top:0;
}

首先,使用正确的类和列表本身对列表中的任何内容进行全局重置。全局重置意味着将所有边距和填充设置为 0,并将边框和列表样式设置为无。这避免了必须处理跨浏览器差异,也使 CSS 文档更短,因为您不需要为每个元素重置这些值。

然后将列表和所有列表项浮动到左侧,使它们显示为内联,而不是一个在另一个下面。您需要浮动主列表以确保它包含其他列表。

将图像设置为显示为块元素,以避免它们周围的间隙,并在每个列表项上添加一个像素的填充,以显示背景色。

“照片”列表项需要一个浮动清除来显示在其他项的下面。将其顶部填充设置为 0,以避免较小图像和较大图像之间出现双线。

init()方法的功能类似于上一张幻灯片中的方法。测试 DOM 支持,遍历文档中的所有列表,跳过那些没有正确类的列表。拥有正确类的类作为参数发送给 initShow()方法:

miniSlides.js(节选)

init : function() {
  if( !document.getElementById || !document.createTextNode ) {
    return;
  }
  var lists = document.getElementsByTagName('ul');
  for( var i = 0; i < lists.length; i++ ) {
    if( !DOMhelp.cssjs('check', lists[i],minislides.triggerClass ) ) {
     continue;
    }
    minislides.initShow( lists[i] );
  }
},

initShow()方法通过创建一个新的列表项、创建一个新的图像并将大图像类分配给新的列表项来启动。它将图像作为列表项的子项添加,并将新列表项作为主列表的子项添加。与前面定义的 CSS 一起,它在其他图像下面显示新图像:

miniSlides.js(节选)

initShow : function( o ) {
  var newli = document.createElement('li');
  var newimg = document.createElement('img');
  newli.appendChild( newimg );
  DOMhelp.cssjs('add', newli, minislides.largeImgClass );
  o.appendChild( newli );

然后获取列表中的第一个图像,并读取存储在 alt 属性中的替代文本。将此文本作为替换文本添加到新图像中,将存储在 alternative text 属性中的文本添加到新图像中,并将结果字符串存储为新图像的 title 属性:

miniSlides.js(节选)

var firstPic = o.getElementsByTagName('img')[0];
var alt = firstPic.getAttribute('alt');
newimg.setAttribute('alt', alt );
newimg.setAttribute('title', alt + minislides.alternativeText );

接下来,检索列表中的所有链接,并在用户单击每个链接时应用一个指向 showPic 的事件。将新图像作为名为 photo 的属性存储在 list 对象中,并将新创建的图像的 src 属性设置为第一个链接的目标位置:

miniSlides.js(节选)

  var links = o.getElementsByTagName('a');
  for(i = 0; i < links.length; i++){
   DOMhelp.addEvent( links[i], 'click', minislides.showPic,false );

  }
  o.photo = newimg;
  newimg.setAttribute('src', o.getElementsByTagName('a')[0].href );
},

当链接被点击时,显示链接指向的图片是小菜一碟。在 showPic()方法中,通过 getTarget()检索事件目标,并通过读取列表的 photo 属性获取旧图片。

这一次,您知道访问者将点击的元素是一个图像,这就是为什么您不需要循环和测试元素的名称。相反,您可以转到三个父节点(A、LI 和)并读取先前存储的图像。然后设置可选文本、标题和图像 src,并通过 cancelClick()停止链接的默认行为。添加一个处理程序,在窗口完成加载时触发 init()方法,以完成迷你幻灯片放映。

miniSlides.js(节选)

  showPic : function( e ) {
    var t = DOMhelp.getTarget( e );
    var oldimg = t.parentNode.parentNode.parentNode.photo;
    oldimg.setAttribute('alt', t.getAttribute('alt') );
    oldimg.setAttribute('title', t.getAttribute('alt') +minislides.alternativeText );
    oldimg.setAttribute('src', t.parentNode.getAttribute('href') );
    DOMhelp.cancelClick( e );
  }
}
DOMhelp.addEvent( window, 'load', minislides.init, false );

图像和 JavaScript 概述

对图像和 JavaScript 的介绍到此结束。我希望你不要被可能发生的事情和以这种方式写剧本所需要的东西弄得不知所措。我还希望我已经激发了您使用这些示例的兴趣,看看还有什么是可能的。请记住这些幻灯片以及它们是如何工作的——在本章的最后,我们将回到文档嵌入幻灯片,并让它自动播放。在第十章中,你将看到如何开发一个更大的支持 JavaScript 的图库。

现在,有一些关于图像和 JavaScript 的事情需要记住:

  • 您可以对图像对象进行大量预加载,但这并不是一种万无一失的方法。浏览器缓存设置、损坏的图像链接和代理服务器可能会干扰您的预加载脚本。有一些库可以帮助预加载过程。例如,PreloadJS 可以帮助您预加载图像、声音和其他 JavaScript 文件。如果这些脚本失败,用户可能会卡住或面对不断变化的进度条,这是令人沮丧的。
  • 虽然您可以直接访问每个图像的属性,但这可能不是最好的方法。把视觉效果留给 CSS,你的脚本不会被其他不知道发生了什么的人修改。
  • 自 DHTML 时代以来,CSS 已经走过了漫长的道路,现在,通过提供挂钩来帮助 CSS 设计者可能是一种更好的方法,而不是纯粹通过脚本来实现视觉效果——前面看到的父翻转就是一个例子。

Windows 和 JavaScript

JavaScript 的常见用法是生成新的浏览器窗口和改变当前窗口。这些也是令人讨厌和不安全的做法,因为你永远无法确定你的网页的访问者是否能够处理调整大小的窗口,或者当有新窗口时,她的用户代理是否会通知你。想象一下屏幕阅读器用户在收听你的站点或者文本浏览器用户。

过去,窗口曾被用于未经请求的广告(弹出窗口)和在隐藏窗口中执行代码以进行数据检索(网络钓鱼),这就是为什么浏览器供应商和第三方软件提供商想出了许多软件和浏览器设置来阻止这种滥用。例如,Mozilla Firefox 用户可以选择他们是否想要弹出窗口,以及 JavaScript 可以改变窗口的哪些属性,如图图 6-11 所示。

9781430250920_Fig06-11.jpg

图 6-11 。Mozilla Firefox 中的高级 JavaScript 设置

其他浏览器如 Microsoft Internet Explorer 7 或 Opera 8 不允许在新窗口中隐藏地址栏,并且可以对新窗口施加大小和位置限制。

image 注意这是不同浏览器厂商想要阻止安全漏洞的一个契合点。打开一个没有可见地址栏的新窗口可以让恶意攻击者通过跨站点脚本 (XSS)在第三方站点上打开一个弹出窗口,让它看起来像是属于该站点的,并要求用户输入信息。更多关于 XSS 的信息可以在维基百科上找到:en.wikipedia.org/wiki/Cross-site_scripting

这对于网络冲浪者来说都是好消息,因为他们可以阻止不想要的广告,并且不太可能被愚弄而将他们的数据交给错误的人。这对你来说不是什么好消息,因为这意味着当你想在 JavaScript 中广泛使用 windows 时,你必须测试很多不同的场景。

抛开这些考虑,你仍然可以用 windows 和 JavaScript 做很多事情。每一个支持 JavaScript 的浏览器都为您提供了一个名为 window 的对象,其属性将在下一节中列出,还有一些关于如何使用它们的示例。

窗口属性

以下是窗口对象的属性列表:

image 注意这个部分列表没有显示所有可用的窗口属性。如果某个浏览器不支持某个给定的属性,我会在讨论该属性的地方注明。

  • closed: Boolean,表示窗口是否关闭(只读)
  • defaultStatus:状态栏中的默认状态消息(Safari 不支持)
  • innerHeight:窗口文档部分的高度
  • innerWidth:窗口文档部分的宽度
  • outerHeight:整个窗口的高度
  • 外部宽度:整个窗口的宽度
  • pageXOffset:文档在窗口内的当前水平起始位置(只读)
  • pageYOffset:文档在窗口内的当前垂直起始位置(只读)
  • status:状态栏的文本内容
  • 名称:窗口的名称
  • toolbar:当窗口有工具栏时,返回 visible 属性为 true 的对象的属性(只读)

例如,如果您想要获取窗口的内部大小,您可以使用以下一些属性:

exampleWindowProperties.html(节选)

function winProps() {
  var winWidth = window.outerWidth;
  var winHeight = window.outerHeight;
  var docWidth = window.innerWidth;
  var docHeight = window.innerHeight;
  var message = 'This window is ';
  message += winWidth + ' pixels wide and ';
  message += winHeight + ' pixels high.\n';
  message += 'The inner dimensions are: ';
  message += docWidth + ' * ' + docHeight + ' pixels';
  alert( message );
}

该功能的一些可能输出如图 6-12 中的所示。

9781430250920_Fig06-12.jpg

图 6-12 。读取窗口属性

Internet Explorer 不支持其他属性。这些是滚动条、定位栏、状态栏、菜单栏和个人栏。它们中的每一个都存储了一个具有值为 true 或 false 的只读可见属性的对象。若要测试用户是否打开了菜单栏,请检查对象和属性:

if ( window.menubar && window.menubar.visible == true )
{
 // Code
}

窗口方法

窗口对象也有许多方法,其中一些在前面的章节中已经讨论过了。除了那些提供用户反馈的功能,最常见的功能是打开新窗口和定时执行功能。

用户反馈方式

此处列出的用户反馈方法在第四章中有详细介绍:

  • alert('message '):显示警报
  • 确认('消息'):显示对话框以确认操作
  • 提示('消息','预设'):显示对话框以输入值

打开新窗口

窗户的开关在技术上相当容易。然而,随着浏览器抑制不同的属性和方法,或者编写糟糕的弹出窗口阻止软件甚至阻止“好的”弹出窗口,打开和关闭窗口可能成为一场噩梦,需要适当的测试。打开新窗口的细节非常简单。您有四种方法可供选择:

  • open('url ',' name ',' properties '):打开一个名为“name”的新窗口,在其中加载 url,并设置窗口属性
  • close():关闭窗口(如果窗口不是弹出窗口,这将导致安全警告。)
  • blur():将浏览器的焦点从窗口移开
  • focus():将浏览器的焦点移到窗口

open 方法的属性字符串有一个非常独特的语法:它将窗口的所有属性以字符串的形式列出,每个字符串由一个名称和一个用等号连接的值组成,用逗号分隔:

myWindow = window.open('demo.html', 'my', 'p1=v1,p2=v2,p3=v3' );

并非所有浏览器都支持此处列出的所有属性。例如,移动浏览器可能不具备桌面浏览器的所有功能。但是这个列表会让你知道什么是可用的:

  • height:以像素为单位定义窗口的高度。
  • width:以像素为单位定义窗口的宽度。
  • left:以像素为单位定义窗口在屏幕上的水平位置。
  • top:以像素为单位定义窗口在屏幕上的垂直位置。
  • 位置:定义窗口是否有地址栏(是或否)。出于安全考虑。该选项始终为“是”,并且不可编辑。
  • menubar:定义窗口是否有菜单栏(是或否)。这不适用于 MacOS 上的浏览器,因为菜单总是在屏幕的顶部。
  • resizable:定义当窗口太小或太大时,是否允许用户调整窗口的大小。浏览器已经覆盖了此设置。用户必须更改他们的设置才能使用它。
  • scrollbars:定义窗口是否有滚动条(是或否)。Opera 不允许滚动条被抑制。
  • status:定义窗口是否有状态栏(是或否)。Opera 不允许关闭状态栏。
  • 工具栏:定义窗口是否有工具栏(是或否)。Opera 不支持关闭工具栏。

要打开一个宽 200 像素、高 200 像素、距屏幕左上角 100 像素的窗口,然后将文档 grid.html 加载到其中,您必须设置适当的属性,如下所示:

var windowprops = "width=200,height=200,top=100,left=100";

您可以尝试在页面加载时打开窗口:

examplewindowpopup . html(excerpt)

function popup() {
  var windowprops = "width=200,height=200,top=100,left=100";
  var myWin =  window.open("grid.html", "mynewwin" ,windowprops );
}
window.onload = popup;

请注意,不同浏览器的结果略有不同。Internet Explorer 9 显示没有任何工具栏的窗口;Firefox、Opera 和 Chrome 警告你该页面正试图打开一个新窗口,并询问你是否允许它这样做。Safari 不做任何事情。

当窗口通过链接打开时,处理方式略有不同:

示例链接 WindowPopUp.html

<a href="#" onclick="popup();return false">
  Open grid
</a>

Opera,Firefox,Chrome,Safari 现在都不抱怨弹出窗口了。但是,如果 JavaScript 被禁用,就不会有新窗口,链接也不会做任何事情。因此,您可能希望链接到 href 中的文档,并将 URL 作为参数发送:

exampleParameterLinkWindowPopUp.html(节选)

function popup( url ) {
  var windowprops = "width=200,height=200,top=100,left=100";
  var myWin =  window.open( url, "mynewwin", windowprops );
}

<a href="grid.html" onclick="popup(this.href);return false">Open grid</a>

请注意 window.open()方法的 name 参数。这对于 JavaScript 来说似乎毫无意义,因为它什么也不做。(例如,没有可用于通过 windows.mynewwin 访问窗口的 windows 集合。)但是,它在 HTML target 属性中使用,使链接在弹出窗口而不是主窗口中打开其链接的文档。

在本例中,您将窗口的名称定义为 mynewwin,并将一个链接作为目标,在那里打开www.yahoo.co.uk/:

exampleParameterLinkWindowPopUp.html(节选)

function popup( url ) {
  var windowprops = "width=200,height=200,top=100,left=100";
  var myWin = window.open( url, "mynewwin", windowprops );
}
<a href="http://www.yahoo.co.uk/" target="mynewwin">Open Yahoo</a>

除非您使用非转换的 XHTML 或 strict HTML(不推荐使用 target ),否则您还可以使用值为 _blank 的 target 属性来打开一个新窗口,而不管 JavaScript 是否可用。因此,告诉访问者链接将在链接内的新窗口中打开,以避免混淆或可访问性问题,这是一个很好的做法:

<a href="grid.html" onclick="popup(this.href);return false" target="blank">
Open grid  (opens in a new window)
</a>

然而,因为您可能希望使用 HTML 对使用 JavaScript 的弹出窗口进行更多的控制,所以不依赖于 target 可能是一个更好的解决方案,相反,只有在脚本可用的情况下才把链接变成弹出链接。为此,您需要一些东西来挂钩,并将链接标识为弹出链接。例如,您可以使用名为 popup 的类:

exampleAutomaticPopupLinks.html(节选)

<p><a href="grid.html" class="popup">Open grid</a></p>
<p><a href="http://www.yahoo.co.uk/" class="popup">Open Yahoo</a></p>

计划剧本并不需要太多。您需要该类来触发弹出窗口、文本加载项和作为属性的窗口参数,您需要一个 init()方法来标识链接并添加更改,您需要一个 openPopup()方法来触发弹出窗口:

自动弹出链接. js(骨架)

poplinks = {
  triggerClass : 'popup',
  popupLabel : ' (opens in a new window)',
  windowProps : 'width=200,height=200,top=100,left=100',
  init : function(){ },
  openPopup : function( e ){ },
}

这两种方法非常基本。init()方法检查 DOM 支持,并遍历文档中的所有链接。如果当前链接有 CSS 触发器类,它通过从标签创建新的文本节点并将其作为新的子节点添加到链接来将标签添加到链接。当单击指向 openPopup()方法的链接时,它会添加一个事件,并应用旧版本 Safari 的修复程序来阻止该链接在该浏览器中被跟踪:

automaticPopupLinks.js(节选)

init : function() {
  if( !document.getElementById || !document.createTextNode ) {
   return;
  }
  var label;
  var allLinks = document.getElementsByTagName('a');
  for( var i = 0; i < allLinks.length; i++ ) {
    if( !DOMhelp.cssjs('check', allLinks[i],poplinks.triggerClass ) ) {
      continue;
    }
    label = document.createTextNode( poplinks.popupLabel );
    allLinks[i].appendChild( label );
    DOMhelp.addEvent( allLinks[i], 'click',poplinks.openPopup, false );
    allLinks[i].onclick = DOMhelp.safariClickFix;
  }
},

openPopup()方法检索事件目标,确保它是一个链接,并通过调用 window.open()打开一个新窗口,使用事件目标的 href 属性作为 URL、一个空名称和存储在 windowProps 中的窗口属性。它通过调用 cancelClick()方法停止链接被跟踪来结束:

automaticPopupLinks.js(节选)

openPopup : function( e ) {
  var t = DOMhelp.getTarget( e );
  if( t.nodeName.toLowerCase() != 'a' ) {
    t = t.parentNode;
  }
  var win = window.open( t.getAttribute('href'), '', poplinks.windowProps );
  DOMhelp.cancelClick( e );
}

这个话题还有更多的内容,特别是当涉及到可用性和可访问性问题时,要确保弹出窗口只有在真正打开时才使用,并且不会被某些软件阻止或无法打开。然而,深入这个问题不在当前讨论的范围之内。可以说,在今天的环境中,依靠任何类型的弹出窗口都不容易。

窗口交互

窗口可以使用它们的许多属性和方法与其他窗口进行交互。首先有 focus()和 blur():前者将弹出窗口带到前面;后者将其推到当前窗口的后面。

可以使用 close()方法摆脱窗口,通过 window.opener 属性可以到达打开弹出窗口的窗口。假设您从一个主文档中打开了两个新窗口:

w1 = window.open('document1.html', 'win1');
w2 = window.open('document2.html', 'win2');

通过调用第一个窗口的 blur()方法,可以将第一个窗口隐藏在另一个窗口的后面:

w1.blur();

image 注意你再也看不到它了,但是通过 blur()打开一个不请自来的窗口并立即隐藏它的技巧被称为弹出窗口。这一招被广告商认为没有弹窗那么讨厌,因为它们不覆盖当前页面。如果你曾经发现当你关闭浏览器时,你不记得打开了几个窗口,这可能就是发生过的事情。

您可以通过调用其 close()方法来关闭窗口:

w1.close();

如果您想从弹出窗口中的任何文档进入初始窗口,您可以通过

var parentWin = window.opener;

如果你想从第一个窗口到达第二个窗口,你还需要通过 window.opener,因为第二个窗口是从这个窗口打开的:

var parentWin = window.opener;
var otherWin = parentWin.w2;

请注意,您需要使用分配给窗口的变量名,而不是窗口名。

你可以以这种方式使用任何窗口的任何窗口方法。比方说,你想在 document1.html 关闭 w2;您可以通过调用

var parentWin = window.opener;
var otherWin = parentWin.w2.close();

您也可以调用主窗口的功能。如果主窗口有一个名为 demo()的 JavaScript 函数,您可以从 document1.html 通过

var parentWin = window.opener;
parentWin.demo();

image 警告如果你试图用 window.opener.close()关闭初始窗口,一些浏览器会发出安全警告,询问用户是否允许这样做。这是另一个安全功能,可以防止怀有恶意的网站所有者欺骗不同的网站。许多设计机构过去常常关闭原来的浏览器,转而使用预定义大小的窗口——如果没有前面提到的安全警告,这就不再可能了。这可能是一个好主意,以避免这种行为,除非你想吓唬或骚扰你的访客。

改变窗口和的位置和尺寸

下面列表中的每个方法都有 x 和 y 参数:x 是水平位置,y 是距屏幕左上角以像素为单位的垂直位置。moveBy()、resizeBy()和 scrollBy()方法允许负值,这将使窗口或内容向左上方移动,或者使窗口变小指定的像素数:

  • moveBy(x,y)-将窗口移动 x 和 y 像素
  • moveTo(x,y)-将窗口移动到坐标 x 和 y 处
  • resizeBy(x,y)-按 x 和 y 调整窗口大小
  • resizeTo(x,y)-将窗口大小调整为 x 和 y
  • scrollBy(x,y)-按 x 和 y 方向滚动窗口内容
  • scrollTo(x,y)-将窗口内容滚动到 x 和 y 方向

如果您检查示例文档 exampleWindowPosition.html,您可以测试不同的方法,如图图 6-13 所示。注意,这个例子出现在 Firefox 中。在 Opera 8 或 IE 7 中,小窗口会有一个定位栏。

9781430250920_Fig06-13.jpg

图 6-13 。更改窗口位置和尺寸

带窗口间隔和超时的动画

您可以使用 setInterval()和 setTimeout()窗口方法来允许定时执行代码。setTimeout 表示在执行代码之前等待一定的时间(仅一次);setInterval()在每次经过给定的时间段时执行代码。

  • name = setInterval('someCode ',x):每隔 x 毫秒执行作为 someCode 传递给它的 JavaScript 代码
  • clearInterval( name):取消执行名为 name 的间隔(防止代码再次被执行)
  • name=setTimeout('someCode ',x):等待 x 毫秒后执行一次 JavaScript 代码 someCode
  • clearTimeout( name):如果代码尚未运行,则停止名为 name 的超时

image 注意setInterval()和 setTimeout()中的参数 someCode 是你定义的函数。

使用这些方法的经典例子是新闻收报机、时钟和动画。然而,你也可以用它们来使你的网站不那么突兀,更有益于用户。一个例子是警告消息,它在一定时间后消失。演示 exampleTimeout.html 展示了如何使用 setTimeout()在短时间内显示一个非常明显的警告消息,或者允许用户立即删除它。HTML 有一个段落警告用户文档已经过期:

exampleTimeout.html(节选)

<p id="warning">This document is outdated
 and kept only for archive purposes.</p>

一个基本的样式表将这个警告涂成红色,并为非 JavaScript 用户以粗体显示。对于启用了 JavaScript 的用户,添加一个动态类使警告更加明显。

超时. css

#warning{
  font-weight:bold;
  color:#c00;
}
.warning{
  width:300px;
  padding:2em;
  background:#fcc;
  border:1px solid #c00;
  font-size:2em;
}

两者的区别如图图 6-14 所示。

9781430250920_Fig06-14.jpg

图 6-14 。不含和含 JavaScript 的警告消息

用户可以点击“移除警告”链接来消除警告或等待——它会在 10 秒钟后自动消失。

剧本很简单。您检查 DOM 是否受支持,以及具有正确 ID 的警告消息是否存在。然后将动态警告类添加到消息中,并使用指向 removeWarning()方法的事件处理程序创建一个新链接。您将此链接作为一个新的子节点附加到警告消息中,并定义一个超时,当超过 10 秒时自动触发 removeWarning():

timeout.js(节选)

warn = {
  init : function() {
    if( !document.getElementById || !document.createTextNode ) {
      return;
    }
    warn.w = document.getElementById('warning');
    if( !warn.w ){ return; }
    DOMhelp.cssjs('add', warn.w, 'warning');
    var temp = DOMhelp.createLink('#', 'remove warning');
    DOMhelp.addEvent( temp, 'click', warn.removeWarning, false );
    temp.onclick = DOMhelp.safariClickFix;
    warn.w.appendChild( temp );
    warn.timer = window.setTimeout('warn.removeWarning()', 10000 );
  },

removeWarning()方法需要做的只是从文档中删除警告消息,清除超时,并停止链接的默认操作。

timeout.js(续)

  removeWarning : function( e ){
    warn.w.parentNode.removeChild( warn.w );
    window.clearTimeout( warn.timer );
    DOMhelp.cancelClick( e );
  }
}
DOMhelp.addEvent( window, 'load', warn.init, false )

开创这种效果的一个 web 应用是 Basecamp(可在www.basecamp.com/获得),它在页面加载时用黄色突出显示最近对文档的更改,并逐渐淡出高亮。你可以在 37 个信号(【www.37signals.com/svn/archive…

在网站上使用超时很有诱惑力,因为它们给人一种非常动态的网站的印象,并允许你从一种状态平稳地过渡到另一种状态。

image 提示有几个 JavaScript 效果库可以为你提供预制脚本来实现过渡和动画效果。虽然大部分都很过时,但是也有一些例外,像 script . aculo . us(script.aculo.us/),tween js(www.createjs.com/#!/TweenJS)和 tween lite(www.greensock.com/)。

但是,您可能需要重新考虑在您的网站中使用大量动画和过渡。请记住,代码是在用户的计算机上执行的,根据时间的长短或其他任务的繁忙程度,过渡和动画可能看起来非常笨拙,并成为一种麻烦,而不是更丰富的网站体验。

如果网站的功能依赖于动画,动画也可能是一个可访问性问题,因为它们可能会使一些残疾访问者群体(如认知障碍或癫痫患者)无法使用网站。美国第 508 条可访问性法律(你可以在 www.section508.gov/的[上读到)非常明确地…:](www.section508.gov/)

(h)当显示动画时,应根据用户的选择,以至少一种非动画演示模式显示信息。

www.section508.gov/index.cfm?fuseAction = stdsdoc #软件

然而,对于网站来说,这一点并不清楚。另一方面,万维网联盟(W3C)可访问性指南在二级优先级中明确指出,您应该避免网页中的任何移动:

在用户代理允许用户冻结移动内容之前,避免页面移动。

www.w3.org/TR/WCAG10-TECHS/#tech-avoid-movement

让我们尝试一个允许用户开始和停止动画的例子。以本章前面开发的嵌入式幻灯片放映为例,您将使用 setInterval()添加一个开始和停止自动幻灯片放映的链接,而不是提供向前和向后链接。

HTML 和 CSS 将保持不变,但是 JavaScript 必须有很大的改变。

如果你在浏览器中打开 exampleAutoSlideShow.html,你会看到一个带有播放按钮的幻灯片,当你点击它的时候就开始播放。您可以在页面加载时轻松启动动画,但是最好让用户自己选择。当您需要遵守辅助功能指南时尤其如此,因为未经请求的动画可能会给患有癫痫等残疾的用户带来问题。单击后,该按钮会变成停止按钮,激活时会停止幻灯片放映。你可以在图 6-15 中看到它在 Firefox 中的样子。

9781430250920_Fig06-15.jpg

图 6-15 。将播放按钮改为停止按钮的自动幻灯片放映

从必要的 CSS 类开始,除了 hide 类之外,这些类与第一个幻灯片示例中的相同。因为你这次不会隐藏任何按钮,所以没有必要。

*autolideslowsh . js(except)*的缩写形式

autoSlides = {

  // CSS classes
  slideClass : 'slides',
  dynamicSlideClass : 'dynslides',
  showClass : 'show',
  slideCounterClass : 'slidecounter',

其他属性略有变化。你现在需要播放和停止标签,而不是向后和向前标签。指示图片总数和系列中当前正在显示的图片的计数器保持不变。一个新特性是幻灯片放映的延迟,以毫秒为单位:

autoSlides.js(续)

// Labels
// Play and stop links, you can use any HTML here
playLabel : '<img src="control_play_blue.png" alt="play">',
stopLabel : '<img src="control_stop_blue.png" alt="stop">',
// Counter text, # will be replaced by the current image count
// and % by the number of all pictures
counterLabel : '# of %',

// Animation delay in milliseconds
delay : 1000,

init()方法检查是否支持 DOM,并添加一个名为 slideLists 的新数组,该数组将存储所有要转换成幻灯片的列表。这对于告诉该函数将更改应用到哪个列表是必要的:

autoSlides.js(续)

init : function() {
  if( !document.getElementById || !document.createTextNode ) {
    return;
  }
  var uls = document.getElementsByTagName('ul');
  autoSlides.slideLists = new Array();

首先,遍历文档中的所有列表,并检查该类将它们转换为幻灯片。如果列表具有类,则将 currentSlide 属性初始化为 0,并将循环计数器存储在名为 showCounter 的新列表属性中。同样,这将需要告诉时间间隔要更改哪个列表。您使用 list 作为参数调用 initSlideShow()方法,并将列表添加到 slideLists 数组中:

autoSlides.js(续)

  for( var i = 0; i < uls.length; i++ ) {
    if( !DOMhelp.cssjs('check', uls[i],autoSlides.slideClass ) ){
      continue;
    }
    DOMhelp.cssjs('swap', uls[i], autoSlides.slideClass,_autoSlides.dynamicSlideClass );
    uls[i].currentSlide = 0;
    uls[i]. showIndex = i;
    autoSlides.initSlideShow( uls[i] );
    autoSlides.slideLists.push( uls[i] );
  }
},

initSlideShow()方法与您在 photolistinlineslidessafarifix . js 中使用的同名方法没有太大区别。唯一的区别是您创建了一个链接而不是两个,并将 playLabel 应用为新链接的内容:

autoSlides.js(续)

initSlideShow : function( o ){
  var p, temp ;
  p = document.createElement('p');
  DOMhelp.cssjs('add', p, autoSlides.slideCounterClass );
  o.parentNode.insertBefore( p, o.nextSibling );
  o.play = DOMhelp.createLink('#', ' ' );
  o.play.innerHTML = autoSlides.playLabel;
  DOMhelp.addEvent( o.play, 'click', autoSlides.playSlide, false );
  o.count = document.createElement('span');
  temp = autoSlides.counterLabel.replace( /#/, o.currentSlide + 1 );
  temp = temp.replace( /%/, o.getElementsByTagName('li').length );
  o.count.appendChild( document.createTextNode( temp ) );
  p.appendChild( o.count );
  p.appendChild( o.play );
  temp = o.getElementsByTagName('li')[o.currentSlide];
  DOMhelp.cssjs('add', temp,autoSlides.showClass );
  o.play.onclick = DOMhelp.safariClickFix;
},

playSlide()方法是新的,但是它的开始非常像旧的 showSlide()方法。您检查目标及其节点名称,并检索父列表:

autoSlides.js(续)

playSlide : function( e ) {
  var t = DOMhelp.getTarget( e );
  while( t.nodeName.toLowerCase() != 'a' && t.nodeName.toLowerCase() != 'body' ){
    t = t.parentNode;
  }
  var parentList = DOMhelp.closestSibling( t.parentNode, -1 );

测试父列表是否已经有一个名为 loop 的属性。这是存储 setInterval()实例的属性。您使用列表的属性而不是变量来允许在同一文档中进行多个自动幻灯片放映。

将 setInterval()中使用的字符串定义为 showSlide()方法的调用,并将父列表的 showIndex 属性作为参数。这是必要的,因为 setInterval()是 window 对象的一个方法,不在主 autoSlides 对象的范围内。

使用 setInterval()和 autoSlides.delay 属性中定义的延迟,并将它存储在 loop 属性中,然后将激活的链接内容更改为 Stop 按钮:

autoSlides.js(续)

if( !parentList.loop ) {
  var loopCall = "autoSlides.showSlide('" + parentList.showIndex + " ' )";
  parentList.loop = window.setInterval( loopCall, utoSlides.delay );
  t.innerHTML = autoSlides.stopLabel;

如果列表已经有一个名为 loop 的属性,则幻灯片放映当前正在运行;因此,您清除它,将 loop 属性设置为 null,并将按钮改回 Play 按钮。然后通过调用 cancelClick()来停止默认链接行为。

autoSlides.js(续)

  } else {
    window.clearInterval( parentList.loop );
    parentList.loop = null;
    t.innerHTML = autoSlides.playLabel;
  }
  DOMhelp.cancelClick( e );
},

showSlide()方法发生了很大的变化,但是您将会看到其他方法中一些最初容易混淆的部分(比如 slideLists 数组的优点)使得该方法变得相当简单。

请记住,您在 playSlide()中定义了 interval 应该使用列表的 showIndex 属性作为参数来调用 showSlide()方法。现在,您可以使用这个索引来检索需要循环的列表,只需从 slideLists 数组中检索该列表即可。

autoSlides.js(续)

showSlide : function( showIndex ) {
  var currentShow = autoSlides.slideLists[showIndex];

一旦你有了列表,你就可以读出当前的幻灯片和幻灯片的数量。从当前幻灯片中删除 showClass 以隐藏它:

autoSlides.js(续)

var count = currentShow.currentSlide;
var photoCount = currentShow.getElementsByTagName('li').length;
var photo = currentShow.getElementsByTagName('li')[count];
DOMhelp.cssjs('remove', photo, autoSlides.showClass );

增加计数器显示下一张幻灯片。将计数器与所有幻灯片的数量进行比较,如果已经播放了最后一张幻灯片,则将计数器设置为 0,从而从第一张幻灯片开始重新播放幻灯片。

通过检索列表元素并添加 Show 类来显示幻灯片。更新计数器,并将列表的 currentSlide 属性重置为新的列表元素:

autoSlides.js(续)

    count++;
    if( count == photoCount ){ count = 0 };
    photo = currentShow.getElementsByTagName('li')[count];
    DOMhelp.cssjs('add', photo, autoSlides.showClass );
    var counterText = currentShow.count.firstChild;
    counterText.nodeValue = counterText.nodeValue.replace( /\d/, count + 1 );
    currentShow.currentSlide = count;
  }
}
DOMhelp.addEvent( window, 'load', autoSlides.init, false );

当涉及到动画和代码的定时执行时,这种复杂性只是等待 JavaScript 开发人员的一种体验。创建一个流畅、稳定、跨浏览器的动画现在可以用 JavaScript,以及 CSS3 动画、变换和过渡来完成。幸运的是,有现成的动画库可以帮助您完成这项任务,并且已经由许多使用不同操作系统和浏览器的开发人员进行了稳定性测试。你将通过第十一章中的例子来了解其中一个。

浏览器窗口的导航方法

以下是浏览浏览器窗口的方法列表:

  • back():在浏览器历史记录中后退一页
  • forward():在浏览器历史记录中前进一页
  • home():表现为用户点击了 home 按钮(仅适用于 Firefox 和 Opera 在 IE 中,它的 document.location 是“about:home”)
  • stop():停止在窗口中加载文档(IE 不支持)
  • print():启动浏览器的打印对话框

使用这些方法在页面上提供导航是相当诱人的,这些页面应该通过类似下面的内容简单地链接回上一页:

<a href="javascript:window.back()">Back to previous page</a>

考虑到可访问性和现代脚本,这意味着没有 JavaScript 的用户将得到一些不存在的东西。更好的解决方案是通过服务器端 includes (SSIs) 生成一个真正的“返回上一页”链接,或者提供一个指向正确文档的实际 HTML 超链接。如果两者都不可行,请使用占位符,并在 JavaScript 可用时用生成的链接替换它,如下例所示:

exampleBackLink.html(途经 exampleForBackLink.html)

HTML:
<p id="back">Please use your browser's back button or
keyboard shortcut to go to the previous page</p>
JavaScript:
function backlink() {
  var p = document.getElementById('back');
  if( p ) {
    var newa = document.createElement('a');
    newa.setAttribute('href', '#');
    newa.appendChild( document.createTextNode('back to previous page') );
    newa.onclick = function() { window.back();return false; }
    p.replaceChild( newa, p.firstChild );
  }
}
window.onload = backlink;

image 警告这些方法的危险在于,你提供了浏览器已经提供给用户的功能。不同的是浏览器做得更好,因为它支持更多的输入设备。例如,在 PC 上的 Firefox 中,您可以通过按 Ctrl+P 来打印文档,通过按 Ctrl+W 来关闭窗口或标签,并通过 Alt 和向左或向右箭头键在浏览器历史中向前或向后移动。

更糟糕的是,这些方法提供的功能依赖于脚本支持。由您决定前面的方法——创建调用这些方法的链接,这可能是处理这个问题的最干净的方法——是否值得努力,或者您是否应该让用户决定如何触发浏览器功能。生成链接还具有改变浏览器历史的效果。在这种情况下,您不是导航到浏览器历史记录中的页面,而是向历史记录中添加新页面。

打开新窗口的替代方法:分层广告

有时没有办法避免弹出窗口,因为网站设计或功能需要它们,而由于前面解释的浏览器问题和选项,您不能让它们工作。一个解决方法是层广告,它基本上是放在主要内容之上的绝对定位的页面元素。

让我们试一个例子。假设您的公司希望在页面加载时非常明显地宣传其最新产品。最简单的方法是在文档末尾添加信息,并使用脚本将其转换为层广告。当您使用这种方法时,没有 JavaScript 的访问者仍然可以获得信息,但是如果不给他们机会删除这些信息,他们将无法获得任何覆盖内容的信息。HTML 可以是一个带有 ID 的简单 DIV(为了简洁起见,实际的链接已被替换为“#”):

example layer . html(excerpt)

<div id="layerad">
  <h2>We've got some special offers!</h2>
  <ul>
    <li><a href="#">TDK DVD-R 8x 50 pack $12</a></li>
    <li><a href="#">Datawrite DVD-R 16x 100 pack $50</a></li>
    <li><a href="#">NEC 3500A DVD-RW 16x $30</a></li>
  </ul>
</div>

CSS 设计者可以为非 JavaScript 版本设计广告样式,脚本将添加一个类,允许广告显示在主要内容之上。如果您调用类 dyn,CSS 可能如下所示:

分层. CSS(except)

#layerad{
  margin:.5em;
  padding:.5em;
}
#layerad.dyn{
  position:absolute;
  top:1em;
  left:1em;
  background:#eef;
  border:1px solid #999;
}
#layerad.dyn a.adclose{
  display:block;
  text-align:right;
}

最后一个选择器是动态链接的样式,脚本会将它添加到广告中,允许用户删除它。

剧本本身并不包含任何惊喜。首先,将广告的 ID、动态类以及“关闭”链接的类和文本内容定义为属性:

分层。js】??

ad = {
  adID : 'layerad',
  adDynamicClass : 'dyn',
  closeLinkClass : 'adclose',
  closeLinkLabel : 'close',

init()方法检查 DOM 和 ad,并向其中添加动态类。它创建一个新的链接,并向它添加文本和“关闭”链接的类。它向此链接添加一个指向 killAd()方法的事件处理程序,并在 Ad 的第一个子节点之前插入新链接:

layerAd.js(续)

init : function() {
  if( !document.getElementById || !document.createTextNode ) {
    return;
  }
  ad.offer = document.getElementById( ad.adID );
  if( !ad.offer ) { return; }
  DOMhelp.cssjs('add', ad.offer, ad.adDynamicClass );
  var closeLink = DOMhelp.createLink('#', ad.closeLinkLabel );
  DOMhelp.cssjs('add', closeLink, ad.closeLinkClass );
  DOMhelp.addEvent( closeLink, 'click', ad.killAd,false );
  closeLink.onclick = DOMhelp.safariClickFix;
  ad.offer.insertBefore( closeLink, ad.offer.firstChild );
},

killAd()方法从文档中删除广告并取消链接的默认行为:

layerAd.js(续)

  killAd : function( e ) {
    ad.offer.parentNode.removeChild( ad.offer );
    DOMhelp.cancelClick( e );
  }
}
DOMhelp.addEvent( window, 'load', ad.init, false );

您可以通过在浏览器中打开 exampleLayerAd.html 来测试效果。如果您启用了 JavaScript,您将会看到覆盖内容的广告,如图 6-16 所示。你可以通过使用“关闭”链接来摆脱它。

9781430250920_Fig06-16.jpg

图 6-16 。一个分层广告的例子

弹出窗口的另一个常见用途是在不离开当前页面的情况下显示另一个文档或文件。经典的例子包括一长串无聊的条款和条件或者一张照片。特别是在照片的情况下,弹出窗口是一个次优的解决方案,因为您可以打开一个与图片尺寸相同的窗口,但图像周围会有间隙,因为浏览器的内部样式在正文上有填充设置。您可以通过使用一个带有样式表的空白 HTML 文档来解决这个问题,该样式表将正文边距和填充设置为 0,并通过 JavaScript 将图像添加到窗口中的文档。另一种选择是在覆盖主文档的新生成和定位的元素中显示照片。演示 examplePicturePopup.html 就是这样做的;该脚本需要的只是一个在指向照片的链接上有特定名称的类:

examplePicturePopup.html(节选)

<a class="picturepop" href="pictures/thumbs/dog7.jpg">Sleeping Dog</a>

这个脚本需要做一些之前没有解释过的事情——即读取元素的位置。通过绝对定位元素,用元素覆盖主文档。因为不知道指向照片的链接在文档中的什么位置,所以需要读取它的位置,并在那里显示照片。

但那是以后的事了。首先,您需要预定义属性。您需要一个触发脚本的类,在显示照片时向链接应用一个 link 类,并向包含照片的元素应用另一个类。您还需要定义一个在显示照片时添加到链接的前缀,以及一个充当新创建元素的快捷方式引用的属性:

picturePopup.js (excerpt)

pop={
  triggerClass:'picturepop',
  openPopupLinkClass:'popuplink',
  popupClass:'popup',
  displayPrefix:'Hide',
  popContainer:null,

init()方法检查对 DOM 的支持,并遍历文档的所有链接,测试它们是否有正确的 CSS 类来触发弹出窗口。对于这样做的,该方法添加一个指向 openPopup()方法的事件处理程序,然后将链接的 innerHTML 内容存储在一个预设属性中。

picturePopup.js(续)

init : function() {
  if( !document.getElementById || !document.createTextNode ) {
    return;
  }
  var allLinks = document.getElementsByTagName('a');
  for( var i = 0; i < allLinks.length; i++ ) {
    if( !DOMhelp.cssjs('check', allLinks[i],pop.triggerClass ) ) {
      continue;
    }
    DOMhelp.addEvent( allLinks[i], 'click', pop.openPopup, false );
    allLinks[i].onclick = DOMhelp.safariClickFix;
    allLinks[i].preset = allLinks[i].innerHTML;
  }
},

openPopup()方法检索事件目标并确保它是一个链接。然后它测试是否已经有一个 popContainer,这意味着照片被显示。如果不是这样,该方法将前缀添加到链接的内容中,并添加动态类以使链接看起来不同:

picturePopup.js(续)

openPopup : function( e ) {
  var t = DOMhelp.getTarget( e );
  if( t.nodeName.toLowerCase() != 'a') {
    t = t.parentNode;
  }
  if( !pop.popContainer ) {
    t.innerHTML = pop.displayPrefix + t.preset;
    DOMhelp.cssjs('add', pop.popContainer, pop.popupClass );

然后方法创建一个新的 DIV 作为照片容器,添加适当的类,并添加一个新的图像作为容器 DIV 的子节点。它通过将新图像的 src 属性设置为原始链接的 href 属性的值来显示图像。然后,新创建的照片容器被添加到文档中(作为 body 元素的子元素)。最后,openPopup()调用 positionPopup()方法,将 link 对象作为参数。

picturePopup.js(续)

pop.popContainer = document.createElement('div');
DOMhelp.cssjs('add', t,pop.openPopupLinkClass );
var newimg = document.createElement('img');
pop.popContainer.appendChild( newimg );
newimg.setAttribute('src', t.getAttribute('href') );
document.body.appendChild( pop.popContainer );
pop.positionPopup( t );

如果 popContainer 已经存在,该方法所做的就是调用 killPopup()方法,将链接重置为其原始内容,并删除指示照片已显示的类。调用 cancelClick()可防止链接仅在浏览器中显示照片。

picturePopup.js(续)

  } else {
    pop.killPopup();
    t.innerHTML = t.preset;
    DOMhelp.cssjs('remove', t,pop.openPopupLinkClass );
  }
  DOMhelp.cancelClick( e );
},

positionPopup()方法定义了两个变量(x 和 y),将它们都初始化为 0,然后从元素的 offsetHeight 属性中读取元素的高度。接下来,它读取元素及其所有父元素的垂直和水平位置,并将其与 x 和 y 相加。结果是元素相对于文档的位置。然后,该方法通过将链接的高度与垂直变量 y 相加并改变 popContainer 样式属性,将照片容器定位在原始链接的下方:

picturePopup.js(续)

positionPopup : function( o ) {
  var x = 0;
  var y = 0;
  var h = o.offsetHeight;
  while ( o != null ) {
    x += o.offsetLeft;
    y += o.offsetTop;
    o = o.offsetParent;
  }
  pop.popContainer.style.left = x + 'px';
  pop.popContainer.style.top = y + h + 'px';
},

killPopup()方法从文档中移除 popContainer(通过将该属性的值设置为 null 来清除该属性),并通过调用 cancelClick()来阻止默认链接操作的发生。

image 注意你可以通过调用它的父节点的 removeChild()方法从文档中删除一个节点,把节点本身作为要删除的子节点。但是,因为您使用指向节点的属性,而不是检查节点本身,所以您还需要将此属性设置为 null。

picturePopup.js(续)

  killPopup : function( e ) {
    pop.popContainer.parentNode.removeChild( pop.popContainer );
    pop.popContainer = null;
    DOMhelp.cancelClick( e );
  }
}
DOMhelp.addEvent( window, 'load', pop.init, false );

结果是,你可以点击任何指向正确类别的照片的图像,它会显示下面的图像。图 6-17 显示了一个例子。

9781430250920_Fig06-17.jpg

图 6-17 。一个动态显示照片的例子

这种方法的美妙之处在于,它不仅仅局限于照片。只需简单的修改,您就可以在当前文档中显示其他文档。技巧是向 photoContainer 动态添加一个 IFRAME 元素,并将其 src 属性设置为要嵌入的文档。演示 exampleIframeForPopup.html 正是这样做的,在主文档中显示一个冗长的条款和条件文档。

唯一的区别(除了不同的属性名,因为该方法不显示照片)在于 openPopup 方法,在该方法中添加了新的 IFRAME:

ifrimforpopup . js(excerpt)

var ifr = document.createElement('iframe');
pop.ifrContainer.appendChild( ifr );
ifr.setAttribute('src', t.getAttribute('href') );

图 6-18 显示了这可能是什么样子。

9781430250920_Fig06-18.jpg

图 6-18 。一个动态包含和显示文档的例子

通过 IFRAME 元素包含其他文档是一种简单且受支持的方法,但这不是最容易实现的方法。相反,您可以使用 PHP 之类的服务器端语言来检索文档的内容并将其包含在当前文档中,并使用前面的 layer ad 示例使用的相同技巧。对于更现代的浏览器,你也可以使用 Ajax“动态地”做这件事,但是这将在它自己的章节中解释,第八章。

摘要:Windows 和 JavaScript

传统上,控制当前窗口和打开新窗口是 JavaScript 开发的一大部分,尤其是在 web 应用开发中。然而,近年来,由于对浏览器安全的担忧以及网上冲浪者被大量弹出窗口和安装阻止软件所困扰,使用新窗口变得越来越难——即使你想出于正当理由使用它们。这些和可访问性问题使得使用多个浏览器窗口成为越来越不可靠的 web 通信方法。多亏了这里讨论的一些替换技术(还有 Ajax,我将在第八章讨论)和 Twitter Bootstrap 这样的好框架,现在已经没有必要再使用它们了。以下是关于 Windows 和 JavaScript 需要记住的一些关键事项:

  • 在你尝试做任何事情之前,测试,测试,再测试你打开的窗口是否真的存在。
  • 永远记住,虽然窗口在同一个屏幕上,但它们是浏览器的完全独立的实例。如果您想从一个弹出窗口访问另一个弹出窗口,或者从您打开的任何弹出窗口访问主脚本中的一个功能,您需要通过 window.opener。
  • 不要试图通过拿走工具栏,在屏幕上移动它们,或者通过模糊()和聚焦()来显示和隐藏它们来控制窗口。这些功能大部分现在仍然可用,但很有可能在未来的浏览器中被屏蔽。
  • 您可以使用 window 对象方法模拟许多浏览器行为或交互式元素,如关闭和打印窗口或在浏览器历史记录中向后移动。然而,让用户来选择可能更好。如果您想为用户提供自己的控件,也可以用 JavaScript 创建这些控件。否则,当 JavaScript 不可用时,用户会得到一个你无法遵守的承诺。
  • 如果使用弹出窗口,在打开窗口的链接中告诉访问者,将会有一个新窗口。这就通知了不一定支持多窗口的用户代理的访问者,他们可能需要处理一个变化;它还可以防止访问者意外关闭网站所需的窗口。多年来不请自来的广告和弹出窗口已经让网上冲浪者习惯于立即关闭新窗口,甚至看都不看它们一眼。
  • 通过窗口方法 setTimeout()和 setInterval()使用代码的定时执行有点像化妆:作为一个女孩,你学会如何化妆;作为一个女人,你知道什么时候脱下来。你可以使用这两种方法——它们很容易创造出各种时髦的效果——但是你应该想想你的用户,问问你自己,“当一个静态界面可能更快地导致同样的结果时,真的需要动画吗?”

摘要

干得好!您已经阅读完了这一章,现在应该可以用图像和窗口,或者更好的窗口替换技术来创建自己的 JavaScript 解决方案了。

如果有些例子很难理解,不要沮丧,因为作为 JavaScript 开发人员,你会经常有这种感觉。这并不意味着你不明白。有许多方法可以解决 JavaScript 中的任何问题。虽然有比这里介绍的更简单的方法,但是习惯这种脚本应该会让您为更高级的脚本任务做好准备,比如使用第三方 API 或 web 应用开发。

在下一章,当我们讨论导航和表单时,你有机会更好地熟悉事件和属性处理。

七、JavaScript 和用户交互:导航和表单

在这一章中,我将讨论 JavaScript 的两个更常见的用途:导航和表单。两者都涉及大量的用户交互,因此需要深思熟虑地计划和执行。一个网站的成功与否取决于其导航的难易程度,在 web 上没有什么比一个难以使用或无法填写的表单更令人沮丧的了。

image 注意这一章由大量的代码示例组成,你会被要求在浏览器中打开其中的一些来自己测试功能。因此,如果你还没有去过www.beginningjavascript.com下载这本书的代码示例,现在可能是个好时机。当谈到编码时,我坚定地相信实践培训,当你可以直接体验它或者——更好的是——在你自己的编辑器中摆弄它时,你会更容易理解一些功能。

导航和 JavaScript

自从浏览器开始支持页面元素的外观和感觉的动态变化以来,增加网站导航的趣味一直是 DHTML 的主要任务之一。动画和褪色导航的时代是从 DHTML 开始的。这个想法似乎是,如果网页的导航非常流畅、高科技,并且看起来和操作起来都像星际飞船 Enterprise 上的 LCARS 界面,那么这个网站一定很棒。很多时候,访问者不同意,一旦他们厌倦了导航,他们会使用网站搜索选项,假设提供了一个。

这里就不说华而不实的导航了;相反,我将举例说明如何使用 JavaScript 使页面和站点导航更直观、更简单,同时尽可能保持其可访问性。

页面重载的恐惧

对表单和 web 导航进行了大量 JavaScript 增强,以防止访问者在到达他们想要的信息之前必须重新加载页面或加载大量页面。这是一个令人钦佩的想法,可以奇妙地工作;但是,我们不要忘记,只有当整个文档被加载时,JavaScript 才能到达页面的元素,并且它可以处理文档中已经存在的内容(也就是说,除非您使用 Ajax 动态加载其他内容——下一章将详细介绍)。

这意味着您可以用 JavaScript 创建一个光滑的界面,只显示整个页面内容的一部分,但这也意味着没有 JavaScript 的访问者将不得不处理文档中的全部数据。在您完全沉迷于增强页面之前,请不时关闭 JavaScript,看看您是否能够处理文档中的数据量。

只有少量专门信息的较小文档的好处是,您可以使用浏览器提供给用户的整个工具包:前进和后退、书签和打印。当您使用 JavaScript 进行导航或分页时,可能会破坏这个功能。不太理想的效果是,您必须维护更多的文档,访问者必须分别加载每个文档,因此也增加了服务器流量。

用 JavaScript 来增强网站并不邪恶;这完全是一个适度和了解你的观众的问题。

导航和 JavaScript 基础

导航和 JavaScript 最基本的想法是,你不依赖 JavaScript 来让你的导航工作。根据 JavaScript 的不同,页面或网站导航会将没有 JavaScript 的用户拒之门外,也会将搜索引擎拒之门外。

image 提示如果你必须向一个非技术人员解释为什么一堆 JavaScript:navigate(‘page 2’)链接不是一个好主意,那么后者是一个很好的论据。对于一个甚至不知道如何在浏览器中关闭 JavaScript 的人来说,将禁用 JavaScript 的网站访问者作为一个值得考虑的目标群体是不容易解释的。向他解释“又大又瞎的百万富翁”谷歌不会索引他的网站是一个更容易的卖点。

使用 JavaScript 的一个经典例子是使用选择框来导航,这看起来使导航更容易,但却有可能疏远一大群访问者。选择框很棒,因为它们可以让你在不浪费屏幕空间的情况下提供很多选项。图 7-1 为打开的选择框;对于所有这些选项,封闭的只使用了一行。

9781430250920_Fig07-01.jpg

图 7-1 。使用选择框进行导航

打开演示页面 exampleSelectNavigation.html,并从下拉菜单中选择一个选项。如果您连接到网络并启用了 JavaScript,您将立即被发送到您选择的地址。不幸的是,如果你选择了错误的选项,你没有机会撤销你的选择。如果没有 JavaScript,您可以选择一个选项,但是什么都不会发生。

examples selectnavigation . html

<form>
<p>
  <select onchange="window.location = this.options[this.selectedIndex].value">
    <option value="#">Please select</option>
    <option value="http://www.apress.com">The publisher</option>
    <option value="http://wait-till-i.com">The author's blog</option>
    <option value="http://icant.co.uk">The author's other articles</option>
    <option value="http://onlinetools.org">Scripts and tools by the author</option>
  </select>
</p>
</form>

键盘访问是另一个问题:你通常使用 Tab 键来访问一个选择框,然后按上下键来选择你想要的选项。在本例中,您将不会有这样的机会,因为一旦您按下向下箭头键,您将被转到第一个选项。解决方法是同时按 Alt 和向下箭头键来展开整个列表。然后,您可以使用向上和向下箭头键选择您的选项,并按 Enter 键选择它。但是你知道吗?

解决这些问题的最简单的方法是永远不要使用 change、mouseover 或 focus 事件将数据发送给服务器或将用户发送到不同的 web 位置。很多游客都无法进入,这可能会导致很多挫折。如果没有迹象表明数据正在发送并且页面将会改变,这是特别令人沮丧的。

相反,提供一个真正的提交按钮、一个在服务器端执行相同重定向的脚本,以及一个提交处理程序,以便在 JavaScript 可用时通过 JavaScript 进行重定向。

examples saferselectnavigation . html(excerpt)

<form method="post" action="redir.php">
<p>
  <label for="url">Please select your destination:</label>
  <select id="url" name="url">
    <option value="http://www.apress.com">The publisher</option>
    <option value="http://wait-till-i.com">The author's blog</option>
    <option value="http://icant.co.uk">The author's other articles</option>
    <option value="http://onlinetools.org">Scripts and tools by the author</option>
  </select>
  <input type="submit" value="Make it so!">
</p>
</form>

这个脚本非常简单:您将一个事件处理程序应用到提交时触发方法的第一个表单。该方法从 ID 为 url 的选项列表的 selectedIndex 中读取用户所做的选择,并通过 window.location 对象将浏览器重定向到那里。在下一节中,您将读到更多关于 window.location 对象的内容,在本章的“表单和 JavaScript”一节中,您将读到所有关于 selectedIndex 和 form 对象的内容。

examples saferselectnavigation . js

send = {
  init : function() {
    DOMhelp.addEvent(document.forms[0], 'submit', send.redirect, false);
  },
  redirect : function(e){
    var t = DOMhelp.getTarget(e);
    var url = t.elements['url'];
    window.location.href = url.options[url.selectedIndex].value;
    DOMhelp.cancelClick(e);
  }
}
DOMhelp.addEvent(window, 'load', send.init, false);

将非 JavaScript 用户发送到另一个 URI 的服务器端脚本可以是 PHP 中一个简单的头重定向:

<?php header('Location: ' . $_POST['url']); ?>

如果用户启用了 JavaScript,她就不必往返服务器;相反,她会被立即转到另一个网站。这可以通过设置 window.location.href 属性来实现,该属性是内置浏览器导航的一部分。

浏览器导航

浏览器为您提供了几个对象,您可以使用它们来自动重定向或浏览浏览器的历史记录。在上一章中,您已经遇到了 window.back()方法。窗口对象还提供了窗口.位置和窗口.历史等属性

window.location 对象存储当前元素的 URI,并具有以下属性(在括号中,如果 URI 是www.example.com:8080/index.php?,您会看到提供的返回值 s=JavaScript#searchresults ):

所有这些属性都可以读写。例如,如果您希望将搜索更改为 DOM 脚本,可以通过 window.location.search= '?“DOM 脚本”。浏览器会自动将字符串 URL 编码为 DOM%20scripting。还可以通过更改 window.location.href 属性将用户的浏览器发送到另一个位置。

除了这些属性,window.location 还有两个方法:

  • reload():重新加载当前文档(与单击“重新加载”按钮或同时按 F5 或 Ctrl 和 R 键的效果相同)。
  • replace(URI):将用户发送到 URI,并用另一个替换当前的 URI。当前的 URI 将不再是浏览器历史的一部分。

image 注意注意这与 String 对象的 replace()方法不同,后者用字符串的一部分替换另一部分。

您可以使用 reload()定期刷新页面,从后端加载新内容,而无需用户单击 Reload 按钮。这种功能在基于 JavaScript 的聊天系统中很常见。

使用 replace()可能非常烦人,因为它破坏了用户的后退按钮功能。当她不喜欢你发给她的页面时,她不能回到当前页面。

用户在到达当前页面之前访问过的页面列表存储在 window.history 对象中。这个对象只有一个属性,叫做 length,它存储已经访问过的页面的数量。它有一些您可以使用的方法(下面列表中的最后两个是作为 HTML5 的一部分添加的):

  • back():在浏览器历史中返回上一页。
  • forward():在浏览器历史记录中前进一页。
  • go(n): Go n 根据 n 是正还是负,在浏览器历史中向前或向后移动。您也可以通过 history.go(0)重新加载同一页面。
  • pushState (State,title,url):该方法将数据推入会话历史,其中 state 表示一个充满数据的对象,title 表示页面标题,url 表示添加到历史中的 URL。
  • replaceState (state,title,url):该方法的工作方式与 pushState()相同,但它修改数据,而不是向浏览器的历史记录中添加新数据。

历史对象只允许您导航到其他页面,而不能读出它们的 URIs 或更改它们。该规则的例外是当前页面,当您使用 replace()时,它会从浏览器历史记录中删除。

正如上一章已经解释过的,通过 JavaScript 将用户发送到其他页面,可以有效地模拟浏览器功能,这可能是多余的,或者会彻底迷惑用户。

页面内导航

您可以使用 JavaScript 使同一页面内的导航更有趣,占用的屏幕空间更少。在 HTML 中,您可以通过锚和目标提供页面内导航,它们都是用

image 注意锚的 name 属性在 HTML5 中被弃用,它实际上足以提供一个 ID 来链接锚和目标对。但是,为了确保与旧浏览器的兼容性,在下面的例子中使用它可能是一个好主意。

让我们以一个目录中链接到页面下方不同目标的内部链接列表作为页面内导航的示例:

exampleLinkedAnchors.html(节选)

<h1>X - a tool that does Y</h1>
<div id="toolinfo">
  <ul id="toolinfotoc">
    <li><a href="#info">Information</a></li>
    <li><a href="#demo">Demo</a></li>
    <li><a href="#installation">Installation</a></li>
    <li><a href="#use">Use</a></li>
    <li><a href="#license">License</a></li>
    <li><a href="#download">Download</a></li>
  </ul>
  <div class="infoblock">
    <h2><a id="info" name="info">Information about X</a></h2>
    [... content ...]
    <p class="back">
      <a href="#toolinfotoc">Back to <acronym title="Table of Contents">TOC</acronym></a>
    </p>
  </div>
  <div class="infoblock">
    <h2><a id="demo" name="demo">Demonstration of what X can do</a></h2>
    [... content ...]
    <p class="back">
      <a href="#toolinfotoc">Back to <acronym title="Table of Contents">TOC</acronym></a>
    </p>
  </div>
  [.... more sections ...]
</div>

您可能会想,具有 infoblock 类的 DIV 元素对于页面内导航来说并不是必需的。这只是部分正确,因为微软的 IE 浏览器在命名锚和键盘导航方面有一个非常烦人的错误。

如果您在 Internet Explorer 中打开演示页面 exampleLinkedAnchors.html,通过按 Tab 键浏览不同的菜单项,并通过按 Enter 键选择您想要的部分,浏览器将被发送到您选择的锚点。但是,Internet Explorer 不会将键盘焦点发送到此锚点。如果您再次按 Tab 键,您不会到达文档中的下一个链接;相反,你会被送回菜单。

您可以通过在具有定义宽度的元素中嵌套锚来解决这个问题。这就是 div 的作用。你可以在 exampleLinkedAnchorsFixed.html 的演示页面上测试一下。实际结果是,您可以将这些元素(在本例中是 div)用于 CSS 样式。

image 提示默认情况下,辅助功能在不同的浏览器中可能是关闭的。例如,要在 MacOS 上的 Safari 中重新打开它,请前往“偏好设置”、“高级”、“辅助功能”。选择按下标签以高亮显示网页上的每个项目选项。

现在让我们用一个脚本来复制和改进这个功能。脚本应该做的是显示菜单,但隐藏所有部分,只显示您选择的部分,以使页面更短,不至于让人不知所措。逻辑很简单:

  • 遍历菜单中的链接,并添加 click 事件处理程序来显示与菜单项相关的部分。
  • 在事件侦听器方法中,隐藏前面显示的部分,显示当前部分。
  • 初始化页面时,隐藏所有部分并显示第一个部分。

然而,这并没有考虑到页面内导航的另一个方面:页面可能被来自另一个链接的预定义目标所请求。通过在浏览器中向 URI 添加锚点来尝试一下,例如,examplelinkedanchorsfied . html # use。您将自动向下滚动到使用部分。您的脚本应该考虑这个用例。

让我们从定义脚本的框架开始。这个脚本的主要对象被称为 iv,用于内部导航——因为 in 是 JavaScript 中的保留字,您希望保持简短。你需要几个属性:

  • 一个 CSS 类来定义菜单何时是 JavaScript 增强的
  • 突出显示菜单中当前链接的 CSS 类
  • 显示当前部分的 CSS 类

image 提示你不需要通过 JavaScript 隐藏这些部分,但是你可以使用上一章描述的 CSS 父类技巧。

您需要为要添加 CSS 类的父元素定义属性,并为能够遍历链接的菜单定义 ID。

还需要两个属性:一个存储当前显示的部分,另一个存储当前突出显示的链接。

就方法而言,您需要一个 init()方法、一个获取当前部分的事件监听器以及一个隐藏前一部分并显示当前部分的方法。

内部名称. js(骨架)

iv = {
  // CSS classes
  dynamicClass : 'dyn',
  currentLinkClass : 'current',
  showClass : 'show',

  // IDs
  parentID : 'toolinfo',
  tocID : 'toolinfotoc',

  // Global properties
  current : null,
  currentLink : null,

  init : function(){ },
  getSection : function(e){ },
  showSection : function(o){ }
DOMhelp.addEvent(window, 'load', iv.init, false);

init()方法首先检查 DOM 是否受支持以及所有必要的元素是否可用。只有这样,您才可以通过 CSS 将类添加到父元素中,以自动隐藏所有的 section 元素。

内部名称. js(except)

init : function(){
  if(!document.getElementById || !document.createTextNode) {
    return;
  }
  iv.parent = document.getElementById(iv.parentID);
  iv.toc = document.getElementById(iv.tocID);
  if(!iv.parent || !iv.toc) { return; }
  DOMhelp.cssjs('add', iv.parent, iv.dynamicClass);

在变量 loc 中存储一个可能的 URL 散列,并开始遍历菜单中的所有链接。替换哈希值中的#使得以后使用它更容易,因为您可以在 getElementById()中使用该名称,而不必删除哈希。

innerNav.js(续)

var loc = window.location.hash.replace('#', ' ');
var toclinks = iv.toc.getElementsByTagName('a');
for(var i = 0; i < toclinks.length; i++) {

将当前链接的 href 属性与 loc 进行比较,如果相同,则将链接存储在 current link 属性中。这里使用的 string replace()方法从 href 属性中删除除锚点名称之外的任何内容。这是必要的,因为在 Internet Explorer 等一些浏览器中,getAttribute('href ')会返回包括文件路径在内的整个链接位置(这在 IE 8 中已得到修复),而不仅仅是 HTML href 属性中的内容。

innerNav.js(续)

if(toclinks[i].getAttribute('href').replace(/.*#/, ' ') == loc){
  iv.currentLink = toclinks[i];
}

接下来,添加一个指向 getSection()的 click 事件。请注意,在这个示例脚本中,您不需要停止默认事件—相反,允许浏览器跳转到该部分也会更改地址栏中的 URI,这反过来允许用户将该部分添加为书签。

innerNav.js(续)

 DOMhelp.addEvent(toclinks[i], 'click', iv.getSection, false);
}

仅当其中一个链接与 URI 中的散列相同时,才定义 currentLink 属性。这意味着如果 URI 没有散列,或者它有一个指向不存在的锚的散列,则需要将 currentLink 定义为菜单中的第一个锚。init()方法通过调用 showSection()方法以 currentLink 作为参数来结束。

innerNav.js(续)

if(!iv.currentLink) {
  iv.currentLink = toclinks[0];
  }
  iv.showSection(iv.currentLink);
},

事件侦听器方法 getSection()不需要做太多事情;它需要做的就是确定哪个链接被点击了,并将其作为参数发送给 showSection()。如果不是需要访问 window.location.hash,这两行可能是 showSection()方法的一部分。

innerNav.js(续)

getSection : function(e) {
  var t = DOMhelp.getTarget(e);
  iv.showSection(t);
},

showSection()方法检索在 init()方法中作为参数 o 单击或定义的 link 对象。第一项任务是读取此链接的 href 属性,并通过正则表达式删除散列符号之前和包括散列符号在内的所有内容来检索锚点名称。然后,通过读取带有锚 ID 的元素并在节点树中向上移动两个节点,检索要显示的部分。

innerNav.js(续)

showSection : function(o) {
  var targetName = o.getAttribute('href').replace(/.*#/,'');
  var section = document.getElementById(targetName).parentNode.parentNode;

为什么有两个节点?如果您还记得 HTML,您将链接嵌套在标题中,并将标题和部分的其余部分嵌套在 DIV 元素中:

exampleLinkedAnchors.html(节选)

<li><a href="#demo">Demo</a></li>
 [... code snipped ...]
 <div class="infoblock">
   <h2><a id="demo" name="demo">Demonstration of what X can do</a></h2>

因为 getElementById('demo ')提供了链接,所以一个向上的节点是 H2,另一个向上的节点是 DIV。

然后,您需要检查是否显示了一个旧的部分和一个突出显示的链接,然后通过删除适当的类来删除突出显示并隐藏该部分。然后添加当前链接和当前节的类,并设置属性 current 和 current link,确保下次调用 showSection()时撤销您现在所做的操作。

innerNav.js(续)

    if(iv.current != null){
      DOMhelp.cssjs('remove', iv.current, iv.showClass);
      DOMhelp.cssjs('remove', iv.currentLink, iv.currentLinkClass);
    }
    DOMhelp.cssjs('add', section, iv.showClass);
    DOMhelp.cssjs('add', o,iv.currentLinkClass);
    iv.current = section;
    iv.currentLink = o;
  }
}
DOMhelp.addEvent(window, 'load', iv.init, false);

如果您将这个脚本应用到演示 HTML 页面并添加一个适当的样式表,当您单击链接时,您会得到一个显示不同部分的更短的页面。通过在浏览器中打开 exampleLinkedAnchorsPanel.html,您可以亲自看到这一点。在 Firefox 17.0.1 的 MacOS 上,页面看起来就像你在图 7-2 中看到的那样。

9781430250920_Fig07-02.jpg

图 7-2 。从锚-目标列表创建的面板界面

简单地应用一个不同的样式表将页面变成一个选项卡式界面,正如你在 exampleLinkedAnchorsTabs.html 和图 7-3 中看到的。

9781430250920_Fig07-03.jpg

图 7-3 。从锚-目标列表创建的选项卡式界面

对于一个简短的脚本来说,这是非常简洁的;然而,每当用户单击其中一个选项时,清除链接 href 并检索该部分似乎是重复的。

完成相同任务的另一种方法是将链接和部分存储在两个关联数组中,并简单地为 showSection()提供要显示和突出显示的锚的名称。演示 exampleLinkedAnchorsTabsNamed.html 使用了这种技术,并展示了如何同时应用鼠标悬停处理程序来获得相同的效果。

内联 name . js】??

iv = {
  // CSS classes
  dynamicClass : 'dyn',
  currentLinkClass : 'current',
  showClass : 'show',

  // IDs
  parentID : 'toolinfo',
  tocID : 'toolinfotoc',

第一个变化是您只需要一个当前属性和两个新的数组属性,称为 sections 和 sectionLinks,它们将在以后存储节和链接。

innerNavNamed.js(续)

// Global properties
current : null,
sections : [],
sectionLinks : [],
init : function() {
  var targetName,targetElement;
  if(!document.getElementById || !document.createTextNode){
    return;
  }
  var parent = document.getElementById(iv.parentID);
  var toc = document.getElementById(iv.tocID);
  if(!parent || !toc) { return; }
  DOMhelp.cssjs('add', parent, iv.dynamicClass);
  var toclinks = toc.getElementsByTagName('a');
  for(var i = 0; i < toclinks.length; i++){

除了单击之外,还要添加一个 mouseover 处理程序,并将 href 属性存储在菜单中每个链接的一个名为 targetName 的属性中。

innerNavNamed.js(续)

DOMhelp.addEvent(toclinks[i], 'click', iv.getSection, false);
DOMhelp.addEvent(toclinks[i], 'mouseover', iv.getSection,false);
targetName = toclinks[i].getAttribute('href').replace(/.*#/,'');
toclinks[i].targetName = targetName;

通过将第一个链接存储在 presetLink 变量中,将其定义为当前活动链接,并确定锚是否指向现有元素。如果是,则将元素存储在 sections 数组中,将链接存储在 sectionLinks 数组中。请注意,这会产生一个关联数组,这意味着您可以通过 section['info']到达第一节。

innerNavNamed.js(续)

  if(i == 0){ var presetLink = targetName; }
  targetElement = document.getElementById(targetName);
  if(targetElement) {
    iv.sections[targetName] = targetElement.parentNode.parentNode;
    iv.sectionLinks[targetName] = toclinks[i];
  }
}

然后,您可以从 URI 散列中获得一个可能的锚名,并使用该锚名或 presetLink 中存储的锚名调用 showSection()。

innerNavNamed.js(续)

  var loc = window.location.hash.replace('#', ' ');
  loc = document.getElementById(loc) ? loc : presetLink;
  iv.showSection(loc);
},

getSection()事件用链接的 targetName 属性值调用 showSection()。这个属性是在前面的 init()方法中设置的。

innerNavNamed.js(续)

getSection:function(e){
  var t = DOMhelp.getTarget(e);
  iv.showSection(t.targetName);
},

所有这些都使 showSection()变得轻而易举,因为重置最后一个链接和部分并设置当前链接和部分所需要做的就是使用数组到达正确的元素并添加或删除 CSS 类。当前节存储在一个名为 current 的属性中,而不是存储在节和链接的属性中。

innerNavNamed.js(续)

  showSection : function(sectionName){
    if(iv.current != null){
      DOMhelp.cssjs('remove', iv.sections[iv.current], iv.showClass);
      DOMhelp.cssjs('remove', iv.sectionLinks[iv.current], iv.currentLinkClass);
    }
    DOMhelp.cssjs('add', iv.sections[sectionName], iv.showClass);
    DOMhelp.cssjs('add', iv.sectionLinks[sectionName], iv.currentLinkClass);
    iv.current = sectionName;
  }
}
DOMhelp.addEvent(window, 'load', iv.init, false);

页面内导航有更多的选项,例如,你可以提供“上一页”和“下一页”链接,而不是“上一页”链接来浏览选项。如果你想看到这样的脚本,并且每页提供几个标签导航,你可以在 onlinetools.org/tools/domta… DOMtab。

网站导航

网站导航和页面内部导航完全不同。您现在一定已经厌倦了阅读它,但是对于依赖于 JavaScript 的导航来说,没有什么好的理由。是的,您可以使用 JavaScript 自动将用户发送到其他位置,但这不是一种安全的方法,因为像 Opera 和 Mozilla 这样的浏览器允许用户阻止这一点。(恶意网站过去使用重定向将用户发送到垃圾网站。)此外,作为站点维护者,它剥夺了您使用站点度量软件的机会,该软件计算点击次数并记录您的访问者在站点中的行程,因为不是所有的度量包都计算 JavaScript 重定向。

由于这些原因,站点导航基本上被限制为增强菜单的 HTML 结构的功能,并通过事件处理程序添加功能。用户到其他页面的真正重定向仍然需要通过链接或表单提交来实现。

网站菜单的一个非常合乎逻辑的 HTML 结构是嵌套列表:

exampleSiteNavigation.html(节选)

<ul id="nav">
  <li><a href="#">Home</a></li>
  <li><a href="#">Products</a>
    <ul>
      <li><a href="#">CMS solutions</a>
        <ul>
          <li><a href="#">Mini CMS</a></li>
          <li><a href="#">Enterprise CMS</a></li>
        </ul>
      </li>
      <li><a href="#">Company Portal</a></li>
      <li><a href="#">eMail Solutions</a>
        <ul>
          <li><a href="#">Private POP3/SMTP</a></li>
          <li><a href="#">Listservers</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#">Services</a>
    <ul>
      <li><a href="#">Employee Training</a></li>
      <li><a href="#">Auditing</a></li>
      <li><a href="#">Bulk sending/email campaigns</a></li>
    </ul>
  </li>
  <li><a href="#">Pricing</a></li>
  <li><a href="#">About Us</a>
    <ul>
      <li><a href="#">Our offices</a></li>
      <li><a href="#">Our people</a></li>
      <li><a href="#">Jobs</a></li>
      <li><a href="#">Industry Partners</a></li>
    </ul>
  </li>
  <li><a href="#">Contact Us</a>
    <ul>
      <li><a href="#">Postal Addresses</a></li>
      <li><a href="#">Arrange Callback</a></li>
    </ul>
  </li>
</ul>

原因是,即使没有任何样式表,导航的结构和层次对访问者来说也是显而易见的。您还可以轻松地设计导航样式,因为所有元素都包含在更高层次的元素中,这允许使用上下文选择器。

image 注意我们不会在这里讨论在导航中提供网站的每一页是否有意义(因为传统上这是网站地图的工作)。在下一章,我们将再次讨论这个话题,并提供给用户一个选择。

基本的网站可用性和常识决定了当前显示的页面不应该链接到自身。为了防止这种情况发生,用一个强元素替换当前页面链接,这也意味着没有 CSS 的用户知道他们在导航中的位置,并且您有机会在导航中以不同的方式设置当前页面的样式,而不必求助于 CSS 类。使用一个强元素代替一个 SPAN 也意味着没有 CSS 的用户可以得到一个明显的指示,表明哪个项目是当前项目。

例如,在迷你 CMS 页面上,导航如下:

exampleHighlightedSiteNavigation.html(节选)

<ul id="nav">
  <li><a href="#">Home</a></li>
  <li><a href="#">Products</a>
    <ul>
      <li><a href="#">CMS solutions</a>
        <ul>
          <li><strong>Mini CMS</strong></li>
          <li><a href="#">Enterprise CMS</a></li>
        </ul>
      </li>
      </ul>
    </li>

您必须在服务器端这样做,因为在 JavaScript 中突出显示当前页面是没有意义的(当然,通过将所有 link href 属性与 window.location.href 进行比较,这并不难做到)。

期望这个 HTML 结构允许你创建一个类似资源管理器的展开和折叠菜单。当你点击包含其他项目的菜单项时,它应该显示或隐藏它的子项目。然而,脚本的逻辑可能与您预期的有点不同。首先,您不必遍历菜单的所有链接。相反,您需要执行以下操作:

  1. 向隐藏所有嵌套列表的主导航项目添加一个 CSS 类。
  2. 循环浏览导航中的所有 UL 项目(因为它们是嵌套的子菜单)。
  3. 向每个 UL 的父节点添加一个指示该列表项包含其他列表的 CSS 类。
  4. 在父节点内的第一个链接上添加一个 click 事件。
  5. 测试父节点是否包含任何强元素,如果有,添加该类以显示 UL,从而防止当前页面所在的子菜单被隐藏。您用一个打开的类替换父类,以显示该部分已经展开。
  6. click 事件侦听器方法需要检查父节点的第一个嵌套 UL 是否有 show 类,如果有,就删除它。它还应该用父类替换开放类。如果没有显示类,它应该做完全相反的事情。

演示文档 exampleDynamicSiteNavigation.html 做到了这一点,用迷你 CMS 页面定义为当前页面来显示效果。图 7-4 显示了这在 MacOS 上的 Firefox 17.0.1 中的样子。

9781430250920_Fig07-04.jpg

图 7-4 。带有 JavaScript 和 CSS 的树形菜单

剧本的框架相当短;您将所有必需的 CSS 类定义为属性,将导航的 ID 定义为另一个属性,并使用 init()和 changeSection()方法来应用整体功能并相应地展开或折叠各个部分。

site navigation . js(skeleton)

sn = {
  dynamicClass : 'dyn',
  showClass : 'show',
  parentClass : 'parent',
  openClass : 'open',
  navID : 'nav',
  init : function() {},
  changeSection : function(e) {}
}
DOMhelp.addEvent(window, 'load', sn.init, false);

init()方法定义了一个名为 triggerLink 的变量,并在应用动态类隐藏嵌套元素之前检查 DOM 支持以及必要的导航元素是否可用。

siteNavigation.js(节选)

init : function() {
  var triggerLink;
  if(!document.getElementById || !document.createTextNode) {
    return;
  }
  var nav = document.getElementById(sn.navID);
  if(!nav){ return; }
  DOMhelp.cssjs('add', nav, sn.dynamicClass);

然后,它遍历所有嵌套的 UL 元素,并将对父节点中第一个链接的引用存储为 triggerLink。它应用调用 changeSection()方法的 click 事件,并将父类添加到父节点。

siteNavigation.js(续)

var nested = nav.getElementsByTagName('ul');
for(var i = 0; i < nested.length; i++){
  triggerLink = nested[i].parentNode.getElementsByTagName('a')[0];
  DOMhelp.addEvent(triggerLink, 'click', sn.changeSection, false);
  DOMhelp.cssjs('add', triggerLink.parentNode, sn.parentClass);
  triggerLink.onclick = DOMhelp.safariClickFix;

该代码测试父节点是否包含强元素,如果是,则将 show 类添加到 UL,将 open 类添加到父节点。这可以防止当前部分被隐藏。

siteNavigation.js(续)

    if(nested[i].parentNode.getElementsByTagName('strong').length > 0){
      DOMhelp.cssjs('add', triggerLink.parentNode, sn.openClass);
      DOMhelp.cssjs('add', nested[i], sn.showClass);
    }
  }
},

所有事件侦听器方法 changeSection()需要做的就是获取事件目标,测试父节点的第一个嵌套 UL 是否应用了 show 类,如果是,则移除该 UL。此外,它需要将父节点的 open 类更改为 parent,反之亦然。

siteNavigation.js(续)

  changeSection : function(e){
    var t = DOMhelp.getTarget(e);
    var firstList = t.parentNode.getElementsByTagName('ul')[0];
    if(DOMhelp.cssjs('check', firstList, sn.showClass)) {
      DOMhelp.cssjs('remove', firstList, sn.showClass)
      DOMhelp.cssjs('swap', t.parentNode, sn.openClass, sn.parentClass);
    } else {
      DOMhelp.cssjs('add', firstList,sn.showClass)
      DOMhelp.cssjs('swap', t.parentNode, sn.openClass, sn.parentClass);
    }
    DOMhelp.cancelClick(e);
  }
}
DOMhelp.addEvent(window, 'load', sn.init,false);

该脚本应用于正确的 HTML,并使用适当的样式表进行样式化,将为您提供展开和折叠导航。CSS 的相关部分如下:

siteNavigation.css(节选)

#nav.dyn li ul{
  display:none;
}
#nav.dyn li ul.show{
  display:block;
}
#nav.dyn li{
  padding-left:15px;
}
#nav.dyn li.parent{
  background:url(plus.jpg) 0 5px no-repeat #fff;
}
#nav.dyn li.open{
  background:url(minus.jpg) 0 5px no-repeat #fff;
}

通过将嵌套的 UL 元素的显示属性值分别设置为“阻止”和“无”,可以显示和隐藏这些元素。这也使得所包含的链接脱离了正常的跳转顺序:如果键盘用户想要到达同一层次上的下一个元素而不展开该部分,他们不必在嵌套列表中的所有链接中跳转。如果他们先按 Enter 键展开该部分,他们将能够使用 Tab 键浏览子菜单链接。

所有 LI 元素都有一个左填充,以允许指示器图像显示该部分有子链接或者它是打开的。具有 open 类或 parent 类的 LI 元素得到一个背景图像来指示它们的状态。

所有这些都很好,但是如果您想提供一个到嵌套部分的父页面的链接呢?解决方案是在每个父链接之前添加一个新的链接图像,它显示和隐藏链接,并保持链接不变。

演示页面 exampleIndicatorSiteNavigation.html 展示了这是什么样子,它是如何工作的。脚本不需要做太多的修改:

siteNavigationIndicator.js(节选)

sn = {
  dynamicClass : 'dyn',
  showClass : 'show',
  parentClass : 'parent',
  openClass : 'open',

第一个变化是您需要两个新的属性来提供要添加到嵌套列表的父节点中的图像。这些将通过 innerHTML 添加,以便于维护人员在需要时用其他图像甚至文本替换它们。

siteNavigationIndicator.js(续)

parentIndicator : '<img src="plus.jpg" alt="open section" title="open section">',
openIndicator: '<img src="minus.jpg" alt="close section" title="close section">',
navID : 'nav',
init : function() {
  var parentLI, triggerLink;
  if(!document.getElementById || !document.createTextNode){
    return;
  }
  var nav = document.getElementById(sn.navID);
  if(!nav){ return; }
  DOMhelp.cssjs('add', nav,sn.dynamicClass);
  var nested = nav.getElementsByTagName('ul');
  for(var i = 0; i < nested.length; i++) {

您没有将父节点中的第一个链接作为触发链接,而是创建了一个新的 link 元素,将其 href 属性设置为一个简单的 hash,使其可点击,并添加前面定义的父指示器图像作为其内容。然后插入链接的图像作为父节点的第一个子节点。

siteNavigationIndicator.js(续)

parentLI = nested[i].parentNode;
triggerLink = document.createElement('a');
triggerLink.setAttribute('href', '#')
triggerLink.innerHTML = sn.parentIndicator;
parentLI.insertBefore(triggerLink, parentLI.firstChild);

init()方法的其余部分几乎保持不变,不同之处在于,当父节点包含一个强元素时,您不仅要应用这些类,还要用“打开的”指示器图像替换“父”指示器图像。

siteNavigationIndicator.js(续)

    DOMhelp.addEvent(triggerLink, 'click', sn.changeSection, false);
    triggerLink.onclick = DOMhelp.safariClickFix;
    DOMhelp.cssjs('add', parentLI, sn.parentClass);
    if(parentLI.getElementsByTagName('strong').length > 0) {
      DOMhelp.cssjs('add', parentLI, sn.openClass);
      DOMhelp.cssjs('add', nested[i], sn.showClass);
      parentLI.getElementsByTagName('a')[0].innerHTML = sn.openIndicator
    }
  }
},

changeSection()方法的不同之处在于,您需要通过将目标的节点名与。

siteNavigationIndicator.js(续)

changeSection : function(e){
  var t = DOMhelp.getTarget(e);
  while(t.nodeName.toLowerCase() != 'a') {
    t = t.parentNode;
  }

该方法的其余部分保持不变,只有一点不同——除了应用不同的类之外,您还更改了链接的内容。

siteNavigationIndicator.js(续)

    var firstList = t.parentNode.getElementsByTagName('ul')[0];
    if(DOMhelp.cssjs('check', firstList, sn.showClass)) {
      DOMhelp.cssjs('remove', firstList, sn.showClass);
      DOMhelp.cssjs('swap', t.parentNode, sn.openClass, sn.parentClass);
      t.innerHTML = sn.parentIndicator;
    } else {
      DOMhelp.cssjs('add', firstList, sn.showClass)
      DOMhelp.cssjs('swap', t.parentNode, sn.openClass, sn.parentClass);
      t.innerHTML = sn.openIndicator;
    }
    DOMhelp.cancelClick(e);
  }
}
DOMhelp.addEvent(window, 'load', sn.init, false);

所有这些只是增强站点导航的一个例子,而且可能是最容易使用的一个。例如,让一个多级下拉导航菜单变得可访问,同时也适用于鼠标和键盘用户,这是一个巨大的任务,不在本书的范围之内,因为它是非常高级的 DOM 脚本。

页码

分页意味着你将一大堆数据分成几页。这通常在后端完成,但是您可以使用 JavaScript 来更快地检查一长串元素。

分页的一个演示是 examplePagination.html,它出现在 MacOS 上的 Firefox 19.0.2 中,如图图 7-5 所示。

9781430250920_Fig07-05.jpg

图 7-5 。对大量数据行进行分页

要操作的内容由同一个 HTML 表的一组行组成,该表具有分页的类。

examplePagination.html(节选)

<table class="paginated">
<thead>
  <tr>
    <th scope="col">ID</th>
    <th scope="col">Artist</th>
    <th scope="col">Album</th>
    <th scope="col">Comment</th>
  </tr>
</thead>
<tbody>
  <tr>
    <th>1</th>
    <td>Depeche Mode</td>
    <td>Playing the Angel</td>
    <td>They are back and finally up to speed again</td>
  </tr>
  <tr>
    <th>2</th>
    <td>Monty Python</td>
    <td>The final Rip-Off</td>
    <td>Double CD with all the songs</td>
  </tr>
  [... and so on ...]
</tbody>
</table>

您在幻灯片演示示例的最后一章中使用了分页,尽管该示例一次显示一个项目。包含几个项目的分页逻辑要复杂得多,但是这个示例应该让您了解可以使用的技巧:

  • 你通过 CSS 隐藏所有的表格行。
  • 您可以定义每页显示多少行。
  • 您显示第一行并生成分页菜单。
  • 这个菜单有一个“上一个”链接和一个“下一个”链接,它有一个计数器告诉用户要显示哪一部分数据以及总共有多少项。
  • 如果当前切片是第一个,则“前一个”链接应该是不活动的;如果是最后一个,则“下一个”链接应该处于非活动状态。
  • “下一个”链接按定义的量增加切片的起始值,“上一个”链接减少起始值。

在这个示例中,您将使用几个属性和五个方法。对这些属性进行注释是一个好主意,这样将来的维护者就可以更容易地根据他们的需要对它们进行修改。

分页。js(骨架)

pn = {
  // CSS classes
  paginationClass : 'paginated',
  dynamicClass : 'dynamic',
  showClass : 'show',
  paginationNavClass : 'paginatedNav',
  // Pagination counter properties
  // Number of elements shown on one page
  Increase : 5,
  // Counter: _x_ will become the current start position
  //          _y_ the current end position and
  //          _z_ the number of all data rows
  Counter : ' _x_ to _y_ of _z_  ',
  // "previous" and "next" links, only text is allowed
  nextLabel : 'next',
  previousLabel : 'previous',

使用一种方法初始化脚本,一种方法生成所需的额外链接和元素,一种方法浏览“页面”(即隐藏当前结果集并显示下一个),一种方法显示当前页面,另一种方法改变分页菜单。

  init : function(){},
  createPaginationNav : function(table){},
  navigate : function(e){},
  showSection : function(table, start){},
  changePaginationNav : function(table, start){}
}
DOMhelp.addEvent(window, 'load', pn.init, false);

喝杯咖啡,吃点饼干,因为这是摆在你面前的一个剧本。不过不要担心——大部分都是简单的逻辑。

init()方法检查是否支持 DOM,并开始遍历文档中的所有表格元素。它测试表是否有正确的类(在 pn.paginationClass 属性中定义)以及它的行数是否多于您希望在每个“页面”上显示的行数(在 pn.increase 属性中定义)。如果其中一个不是这种情况,它将跳过该方法的其余部分——实际上不添加任何菜单。

pagination.js(节选)

init : function() {
  var tablebody;
  if(!document.getElementById || !document.createTextNode){
    return;
  }
  var ts = document.getElementsByTagName('table');
  for(var i = 0;i < ts.length; i++){
    if(!DOMhelp.cssjs('check', ts[i], pn.paginationClass)){
      continue;
    }
    if(ts[i].getElementsByTagName('tr').length < pn.increase+1){
      continue;
    }

因为您想要隐藏的数据行不包括标题行,而只包括那些包含在表体中的数据行,所以您需要告诉其他方法这一点。

最简单的方法是在表的属性中只存储相关的行。获取表中的第一个 TBODY,并将它的所有行存储在 datarows 属性中。您还将所有行数存储在 datarowsize 中,并将当前属性初始化为 null。

这个属性将存储你想要显示的页面的开始。通过将行和行数存储为属性,其他方法可以更容易地从表中检索信息,而不必再次从 DOM 中读取这些信息。

pagination.js(节选)

tablebody = ts[i].getElementsByTagName('tbody')[0];
ts[i].datarows = tablebody.getElementsByTagName('tr');
ts[i].datarowsize = ts[i].datarows.length;
ts[i].current = null;

将动态类应用于表格,从而隐藏所有表格行。调用 createPaginationNav()方法,将对当前表的引用作为参数来添加“上一个”和“下一个”链接,调用 showSection(),将表引用和 0 作为参数来显示第一个结果集。

pagination.js(节选)

    DOMhelp.cssjs('add', ts[i], pn.dynamicClass);
    pn.createPaginationNav(ts[i]);
    pn.showSection(ts[i], 0);
  }
},

createPaginationNav()方法不包含任何意外;它所做的只是创建链接和计数器,并添加指向 navigate()方法的事件处理程序。首先创建一个新的段落元素,并向其中添加 pagination menu 类。

pagination.js(节选)

createPaginationNav : function(table){
  var navBefore, navAfter;
  navBefore = document.createElement('p');
  DOMhelp.cssjs('add', navBefore, pn.paginationMenuClass);

向段落添加一个新链接,将 previousLabel 属性值作为文本内容,并添加一个新的 SPAN 元素,该元素将显示“上一个”和“下一个”链接之间的当前结果集的编号。您将计数器预设为 1 作为初始值,pn.increase 中定义的每页上显示的元素数作为终值,所有数据行数作为总数。添加到新段落的最后一个元素是“下一个”链接。您可以通过 parentNode 和 insertBefore()在表格前添加新段落。

pagination.js(节选)

navBefore.appendChild(DOMhelp.createLink('#', pn.previousLabel));
navBefore.appendChild(document.createElement('span'));
counter=pn.counter.replace('_x_', 1);
counter=counter.replace('_y_', pn.increase);
counter=counter.replace('_z_', table.datarowsize-1);
navBefore.getElementsByTagName('span')[0].innerHTML = counter;
navBefore.appendChild(DOMhelp.createLink('#', pn.nextLabel));
table.parentNode.insertBefore(navBefore, table);

在桌子下面显示同样的菜单会很好。不需要再次重新创建所有这些元素,只需通过 parentNode、insertBefore()和 nextSibling 克隆段落并将其插入到表格之后即可。然后将每个段落的“上一个”和“下一个”链接存储为它们自己的表格属性,以便于在其他方法中更改它们。

pagination.js(节选)

navAfter = navBefore.cloneNode(true);

table.parentNode.insertBefore(navAfter, table.nextSibling);
table.topPrev = navBefore.getElementsByTagName('a')[0];
table.topNext = navBefore.getElementsByTagName('a')[1];
table.bottomPrev = navAfter.getElementsByTagName('a')[0];
table.bottomNext = navAfter.getElementsByTagName('a')[1];

您不能更早地应用事件处理程序,因为 cloneNode()不克隆任何处理程序。现在,您可以将所有处理程序和旧版本 Safari 的修复程序应用到每个链接。此方法的最后一个变化是将计数器存储在属性中,以便其他方法更容易更新它们。

pagination.js(节选)

    DOMhelp.addEvent(table.topPrev, 'click', pn.navigate, false);
    DOMhelp.addEvent(table.bottomPrev, 'click', pn.navigate, false);
    DOMhelp.addEvent(table.topNext, 'click', pn.navigate, false);
    DOMhelp.addEvent(table.bottomNext, 'click', pn.navigate, false);
    table.bottomNext.onclick = DOMhelp.safariClickFix;
    table.topPrev.onclick = DOMhelp.safariClickFix;
    table.bottomPrev.onclick = DOMhelp.safariClickFix;
    table.topNext.onclick = DOMhelp.safariClickFix;
    table.topCounter = navBefore.getElementsByTagName('span')[0];
    table.bottomCounter = navAfter.getElementsByTagName('span')[0];
},

事件监听器方法 navigate()需要检查哪个链接调用了它。第一步是通过 getTarget()检索事件目标,并通过将其节点名称与 a 进行比较来确保它是一个链接(记住,Safari 喜欢将链接内的文本节点作为事件目标发送。)

pagination.js(节选)

navigate : function(e){
  var start, table;
  var t = DOMhelp.getTarget(e);
  while(t.nodeName.toLowerCase() != 'a'){
    t = t.parentNode;
  }

然后,它需要通过测试链接是否具有 href 属性来检查链接是否是活动的。(稍后,您将通过删除 href 属性来关闭“下一个”或“上一个”链接。)如果没有,那就不应该做什么。下一个任务是从激活的链接中找到表。因为在表的上方和下方都有导航,所以需要检查上一个或下一个兄弟节点是否有 table 的节点名,并相应地定义变量 table。

pagination.js(节选)

if(t.getAttribute('href') == null || t.getAttribute('href') == ' '){ return; }
if(t.parentNode.previousSibling && t.parentNode.previousSibling.nodeName.toLowerCase() == 'table') {
  table = t.parentNode.previousSibling;
} else {
  table = t.parentNode.nextSibling;
}

然后确定激活的链接是“下一个”链接还是“上一个”链接,并将 start 定义为表的当前属性加上或减去定义的增量。您调用 showSection(),将检索到的表和起始值作为参数。

pagination.js(节选)

  if(t == table.topNext || t == table.bottomNext){
    start = table.current + pn.increase;
  } else if (t == table.topPrev || t == table.bottomPrev){
    start = table.current - pn.increase;
  }
  pn.showSection(table, start);
},

showSection()方法调用 changePaginationNav()方法来更新链接和计数器,并测试表上是否已经有一个当前参数。如果有,这意味着存在需要删除的数据行。您可以通过遍历存储在 data rows 属性中的数据行部分并删除 showClass()中定义的 CSS 类来消除它们。

pagination.js(节选)

showSection : function(table, start){
  var i;
  pn.changePaginationNav(table, start);
  if(table.current != null){
    for(i=table.current; i < table.current+pn.increase; i++){
      if(table.datarows[i]) {
        DOMhelp.cssjs('remove', table.datarows[i], pn.showClass);
      }
    }
  }

然后,从开始到开始加上预定义的增量进行循环,并添加 CSS 类以在表中显示这些行。注意,您需要测试这些行是否存在;否则,您可能会尝试找到最后一页上没有的行。(想象一个 22 个元素的列表;点击 16–20 页上的“下一页”链接将尝试显示元素 21 至 25。)为了确保下次调用该方法时显示正确的切片,剩下的工作就是将当前属性定义为起始值。

pagination.js(节选)

  for(i = start; i < start + pn.increase; i++){
    if(table.datarows[i]) {
      DOMhelp.cssjs('add', table.datarows[i], pn.showClass);
    }
  }
  table.current = start;
},

如前所述,changePaginationNav()方法使第一页上的“上一页”链接和最后一页上的“下一页”链接不活动。让链接出现但不可点击的技巧是移除 href 属性。

在第一页上,起始值减去预定义的增量会得到一个负数,这很容易测试。当数字大于 0 时,再次添加 href 属性。

pagination.js(节选)

changePaginationNav : function(table, start){
  if(start - pn.increase < 0) {
    table.bottomPrev.removeAttribute('href');
    table.topPrev.removeAttribute('href');
  } else {
    table.bottomPrev.setAttribute('href', '#');
    table.topPrev.setAttribute('href', '#');
  }

如果开始加增加的行数大于,就需要去掉"下一个"链接;否则,您需要激活它。

pagination.js(节选)

if(start + pn.increase > table.rowsize - 2) {
  table.bottomNext.removeAttribute('href');
  table.topNext.removeAttribute('href');
} else {
  table.bottomNext.setAttribute('href', '#');
  table.topNext.setAttribute('href', '#');
}

用适当的值更新计数器。(请记住,start 需要加 1,以便人类更容易理解,并且您需要测试最后一个值不大于现有行数)。至此,您已经从一个普通的数据表创建了一个分页的接口。

pagination.js(节选)

    var counter = pn.counter.replace('_x_', start+1);
    var last = start + pn.increase;
    if(last > table.datarowsize){ last = table.datarowsize; }
    counter = counter.replace('_y_', last)
    counter = counter.replace('_z_', table.datarowsize)
    table.topCounter.innerHTML = counter;
    table.bottomCounter.innerHTML = counter;
  }
}
DOMhelp.addEvent(window, 'load', pn.init, false);

分页的逻辑保持不变,即使您决定显示和隐藏列表项或其他 HTML 结构。您可以通过在“上一个”和“下一个”链接之间显示编号的步骤而不是计数器来使它变得更加复杂,但是我让您自己去尝试一下。

JavaScript 导航概述

用 JavaScript 为站点导航提供动力是对您技能的一种非常诱人的使用,因为它“就在那里”,并且对于拥有花哨界面的客户来说仍然非常令人惊叹。要记住的主要一点是,你应该时不时地关掉 JavaScript,看看你的界面是否还能工作。这同样适用于不使用鼠标,而是尝试键盘。

您可以使用 JavaScript 使大量数据(如深度嵌套的导航菜单)更容易掌握,并以小块的形式呈现给用户。然而,不要忘记,有些用户将获得所有的导航,而不需要你的脚本将它分割成更小的服务。让使用整个站点地图作为数据源的菜单界面成为可选的而不是给定的,这可能是一个好主意。我们将在下一章看看如何做到这一点。

关于 JavaScript 导航,您需要记住的是:

  • 在用户没有单击或激活界面元素的情况下,不要将用户发送到另一个位置或发送表单数据。与其说它有帮助,不如说它令人困惑,甚至可能被认为是一种安全威胁。(如果你能做到这一点,其他任何人也可以将用户发送到一个站点。)
  • 隐藏数据不会让它消失。尽管你可以用一个漂亮的界面让大量数据变得容易消化,但一些用户仍然会在一次服务中获得全部数据,所有用户——包括那些慢速连接的用户——都必须下载所有数据。
  • 利用现有的网络导航模式比发明新的模式要安全得多。例如,使用链接和锚点,很容易就可以将页面内导航变成选项卡式界面。通过 JavaScript 创建所有必要的选项卡会更加麻烦。

表单和 JavaScript

在接下来的页面中,您将学习如何访问、阅读和更改表单及其元素。我不会在这里讨论验证表单的细节,因为我在第九章中专门讨论了数据验证的主题。

然而,我将触及基本的表单可用性,以及一些不好的做法和为什么应该避免它们。首先,让我们看看本章和本书后面的一些例子中使用的形式:

exampleForm.html(节选)

<form method="post" action="send.php">
<fieldset>
  <legend>About You</legend>
  <p><label for="Name">Your Name</label></p>
  <p><input type="text" id="Name" name="Name" /></p>
  <p><label for="Surname">Your Surname</label></p>
  <p><input type="text" id="Surname" name="Surname" /></p>
  <p><label for="email">Your email</label></p>
  <p><input type="email" id="email" value="you@example.com" name="email"></p>
</fieldset>
<fieldset>
  <legend>Your message</legend>
  <p><label for="subject">Subject</label>
  <select id="subject" name="subject">
    <option value="generalEnquiry" selected="selected">General question</option>
    <option value="Webdesign">Webdesign</option>
    <option value="Hosting">Hosting</option>
    <option value="Training">Training</option>
    <option value="Partnership">Partnership</option>
    <option value="other">Other</option>
  </select></p>
  <p><label for="otherSubject">specify other subject</label>
  <input type="text" id="otherSubject" name="otherSubject" /></p>
  <p><label for="Message">Your Message</label></p>
  <p><textarea id="Message" name="Message" cols="20" rows="5"></textarea></p>
</fieldset>
<fieldset>
  <legend>Email options</legend>
  <p><input type="checkbox" name="copyMeIn" id="copyMeIn">
  <label for="copyMeIn">Send me a copy of this email to the above address</label></p>
  <p><input type="checkbox" name="newsletter" value="yes" id="newsletter">
  <label for="newsletter">Sign me up for the newsletter</label></p>
  <p>Newsletter format:
  <input type="radio" name="newsletterFormat" id="newsHtml" value="html" checked="checked">
  <label for="newsHTML">HTML</label>
  <input type="radio" name="newsletterFormat" id="newsPlain" value="plain">
  <label for="newsPlain">Text</label></p>
  <p class="submit"><input type="submit" value="Send Form"></p>
</fieldset>
</form>

image 注意正如你所看到的,这是一个 HTML 4 STRICT 文档的有效形式,如果你想这样做,所有的元素都被关闭以符合 XHTML。还要注意,在符合 XML 的 HTML 中,您需要分别编写单个属性,如 selected 和 checked,分别为 selected="selected "和 checked="checked "。

该表单具有用于将元素分组为逻辑单元的字段集,以及用于将解释文本与特定表单元素相连接的标签。这对表单的可访问性很有帮助,因为它提供了逻辑组织并避免了歧义。

JavaScript 表单基础

在 JavaScript 中达到和改变表单可以通过几种方式实现。和往常一样,DOM 脚本可以通过 getElementsByTagName()和 getElementById()访问表单及其元素,但也有一个名为 forms 的对象包含当前文档中的所有表单。

该对象允许您以三种方式访问文档中的表单:

  • 在索引为整数的数组中,例如,第三个表单为 document.forms[2]。
  • 通过在 name 属性中定义为对象的名称,例如 document.forms.myForm。
  • 同样的表单数组也可以作为关联数组或散列来访问,例如 document.forms['myForm']。当名称包含特殊字符或空格,并且您不能将其标记为对象时,这是必要的。

表单属性

forms 对象本身只有一个属性 length,它存储文档中表单的数量。

但是,每种形式都有更多可以使用的属性,所有这些属性都可以读取和更改:

  • 动作:提交表单时表单数据发送到的脚本
  • 编码:表单元素的 enctype 属性中定义的表单编码
  • 方法:表单的提交方法 POST 或 GET
  • name:在 name 属性中定义的表单名称(不是在 id!)
  • target:表单数据应该发送到的目标(如果使用框架或多个窗口,这一点很重要)

表单方法

表单对象只有两种方法:

  • reset():将表单重置为初始状态,这意味着用户所做的所有输入和选择都将被撤消,表单将显示在单个元素的 value、selected 或 checked 属性中定义的初始值。请注意不同之处——reset()并不清除表单,而是将其恢复到初始状态。这与用户激活表单中的重置按钮时获得的效果相同。
  • submit():提交表单。

这两种方法都模拟了浏览器的功能——即激活重置或提交按钮——并且您应该确保不要使用它们来剥夺用户必要的交互性。当用户单击提交按钮或按下键盘上的 Enter 键时,表单会被提交,这是一个很好的理由——这是最容易访问的方式,如果您劫持了这个功能并在用户与其他元素交互时提交表单,您可能会迫使他们过早地提交表单。

表单元素

forms 集合中的每个表单都有一个名为 elements,的属性,它实质上是这个表单中所有表单元素的数组(与所有 HTML 元素相反)。您可以像最初访问表单一样访问元素,通过索引号、对象名或关联数组:

  • var elm = document.forms[0]。元素[2];
  • var elm = document . forms . my form . elements . my element;
  • var elm = document . forms . my form . elements[' my element '];

正如您在前面的例子中看到的,您可以混合和匹配符号。您也可以使用变量来代替括号中的索引号或字符串。

elements 集合本身有一个名为 length 的只读属性。例如,您可以使用该属性遍历表单中的所有元素,并读出它们的类型:

var myForm = document.forms[0];
var formElements = myForm.elements;
var all = formElements.length;
for(var i = 0; i < all; i++) {
  alert(formElements[i].type);
}

集合中的每个元素都有几个属性;支持哪些类型取决于元素的类型。我现在将列出所有属性,并在括号中列出支持该属性的元素。我们将在本章后面详细讨论不同的元素:

  • checked:布尔值,表示元素是否被选中(按钮、复选框、单选按钮)
  • defaultChecked:布尔值,表示元素最初是否被选中(复选框、单选按钮)
  • value:value 属性中定义的元素值(除选择框外的所有元素)
  • defaultValue:元素(文本框、文本区域)的初始值
  • 表单:元素所在的表单(只读-所有元素)
  • 名称:元素的名称(所有元素)
  • 类型:元素的类型(只读-所有元素)

一种特殊的元素类型是选择框,它自带一个集合,一个名为 options 的属性——稍后将详细介绍。每个元素都有一系列方法,这些方法也取决于元素的类型。这些方法都不需要任何参数。

  • blur():将用户代理的焦点从元素(所有元素)上移开
  • focus():将用户代理的焦点放在元素上(所有元素)
  • click():模拟用户单击元素(按钮、复选框、文件上传字段、重置和提交按钮)
  • select():选择并突出显示元素的文本内容(密码字段、文本字段和文本区域)

image 注意注意 click()乍一看似乎有点奇怪,但是如果你在 web 应用上工作,并且你的开发环境的中间层处理表单的提交过程,比如 Java Spring 和。NET do。不过,这不是 JS 初学者的环境,所以它超出了本书的范围。

元素集合中不包含的 HTML 属性

除了使用 elements 集合的属性之外,一旦通过 forms 和 elements 集合访问到相关元素,还可以读取和设置(当然,浏览器设置允许)该元素的属性。例如,您可以通过更改文本字段的 cols 和 rows 属性来更改其大小:

var myTextBox = document.forms[0].elements[2];
if (myTextBox.type == 'textarea'){
  myTextBox.rows = 10;
  myTextBox.cols = 30;
}

image 提示在试图设置元素的属性之前,最好检查一下你正在操作的元素的类型,以防它们对这个元素不可用。例如,SELECT 元素没有 cols 或 rows 属性。

全局支持的属性

所有表单元素最初都支持类型、名称、表单和值属性。最近的一个变化是,文件上传字段不再支持设置值,因为当受感染计算机上的用户上传某些内容时,这将允许恶意脚本程序注入她自己的文件以上传到您的服务器。

当您通过 DOM 方法直接访问元素时,使用 form 可以非常方便地到达父表单。例如,通过 DOM: 访问示例表单中的电子邮件字段

var mail = document.getElementById('email');

您可以通过 mail.form 或 mail . parent node . parent node . parent node . parent node 来访问表单以更改其属性或提交表单。

exampleForm.html(节选)

<form method="post" action="send.php">
<fieldset>
  [... code snipped ...]
  <p><input type="text" id="email" value="you@example.com" name="email"></p>
</fieldset>
  [... code snipped ...]
</form>

根据元素在表单中的嵌套深度,使用 form 可以在计算节点数时省去很多麻烦,并且实际上使脚本更容易维护,因为您独立于 HTML。如果您想专门使用节点遍历,也可以使用递归循环来检查父节点的 nodeName,以实现与 HTML 标记相同的独立性:

var mail = document.getElementById('email');
parentForm = mail.parentNode;
while(parentForm.nodeName.toLowerCase() != 'form') {
  parentForm = parentForm.parentNode;
}

使用模糊()和聚焦()

您可以使用 blur()将用户代理的焦点从元素上移开,或者使用 focus()来设置它。这样做的危险在于,blur()并不接受它应该将焦点设置到的任何目标,这意味着用户代理可能会关注下一个元素、浏览器的地址栏或任何他们喜欢的东西。对于使用鼠标的视力正常的用户来说,这不是什么大问题;但是,依赖辅助技术的盲人用户或键盘用户将很难再次找到文档。

当您浏览一些网站的代码时,可能会遇到类似这样的情况:

<a href="#" onclick="dothings();" onfocus="this.blur()">Home</a>

开发人员过去这样做是为了阻止浏览器在当前链接周围显示蓝框或虚线边框。这是一个非常糟糕的想法,因为键盘用户不知道当按下 Enter 键时她当前能够到达哪个元素。

使用 focus()有一些合理的理由;然而,在大多数情况下,改变表单输入的自动顺序并不是一个好主意。不是每个用户都可以看到表单,甚至可以看到表单的用户也可能不会看表单。

特别是对于需要输入大量不同数据的较长表单,您会发现人们不看屏幕,而是在阅读打印输出或信用卡、护照等数据时触摸输入。检查中间的表单并意识到您没有填写正确的字段,或者您仍然停留在先前弹出的错误消息中,这是非常令人沮丧的。

文本字段、文本区域、隐藏字段和密码字段

文本字段、文本区域、隐藏字段和密码字段可能是您必须处理的最常见的字段,因为它们是用户输入文本内容的字段。

除了支持全局表单元素属性,它们还支持元素属性 value 和 defaultValue。不同之处在于,如果用户更改元素的内容,它会更改 value 属性,但不会更改 defaultValue。这也意味着当您更改字段的值时,更改是可见的,但当您更改 defaultValue 时,更改是不可见的。如果您想要更改元素的默认值并使其可见,您需要在之后立即调用 reset()方法。在示例文档中,电子邮件字段有一个默认值:

<p><input type="text" id="email" value="you@example.com"  name="email"></p>

您可以像这样读取值和默认值:

var mail= document.getElementById('email');
alert(mail.defaultValue);
alert(mail.value);

当用户没有在字段上做任何改变时,两个值都是 you@example.com。但是,如果用户在字段中输入 me@otherexample.com 的,这两个值就会不同。

很难找到一个 defaultValue 的例子,它不需要您用 JavaScript 做一些应该是后端工作的事情。一个例子是测试当前域是否是德国的,并将默认的电子邮件字段更改为德国的电子邮件地址。文档加载后,应执行以下代码:

var mail = document.getElementById('email');
if(window.location.host.indexOf('.de') != -1) {
  mail.defaultValue = 'email@adresse.de';
  mail.form.reset();
}

请注意,您需要调用 reset()来使更改可见。你可以看到 exampleFormGermanPreset.html 正在发生的变化。(我在那里作弊,通过排除主机测试使其可见。)

image 注意对于 TEXTAREA 元素,您可以像对任何其他表单文本元素一样读写值和 defaultValue。但是,HTML 标记没有 value 属性—初始值和更改后的值是包含在开始和结束标记之间的文本。

文本元素允许使用名为 select()的方法,该方法突出显示元素中的所有文本,以便于复制和粘贴文本示例。这通常被视为网络杂志或在线文档系统的一个特征。

检查框

复选框是提供明确的“是”或“否”选择的好方法。它们很容易在服务器端读出。(如果有复选框,表单发送带有复选框值的名称,如果没有复选框,表单使用“on ”,当用户没有选中复选框时,表单根本不发送名称。)例如,它比单选按钮组或带有“是”和“否”选项的选择框更容易使用。

除了具有前面描述的全局元素属性之外,复选框还具有 checked 和 defaultChecked 属性,这两个属性都是布尔值,指示是否选择了该选项。您可以读取和写入这两个属性,但是您需要重置窗体以使对 defaultChecked 的更改可见。

JavaScript 与复选框相关的一个常见用途是为用户提供选择所有复选框或撤销在许多复选框中所做选择的机会——一个例子是它们在基于 web 的电子邮件系统中的使用,如图 7-6 所示。这些函数的逻辑非常简单:遍历所有元素,测试每个元素的类型,并相应地更改检查的属性。你可以在 exampleFormCheckboxes.html 看到这样的演示。

9781430250920_Fig07-06.jpg

图 7-6 。通过 JavaScript 批量更改复选框

示例中有三个按钮,当用户单击它们时,它们调用同一个函数—change box()。每个按钮为函数的唯一参数提供不同的数值—1 表示全选,1 表示反转,0 表示不选。

exampleFormCheckboxes.html(节选)

<input type="button" onclick="changeBoxes(1)" value="select all">
<input type="button" onclick="changeBoxes(-1)" value="invert selection">
<input type="button" onclick="changeBoxes(0)" value="select none">

这允许你保持简单的改变复选框的功能。只需遍历页面中第一个表单中的所有元素。如果元素类型不是 checkbox,则继续循环而不执行其余部分。

如果元素是复选框,则确定 action 是否小于 0,并通过在 checked 为 true 时将 checked 更改为 false 来反转复选框状态,反之亦然。如果 action 等于或大于 0,只需将 checked 属性设置为 action 的值。

exampleFormCheckboxes.js(节选)

function changeBoxes(action) {
  var f = document.forms[0];
  var elms = f.elements;
  for(var i = 0; i < elms.length; i++) {
    if(elms[i].type != 'checkbox'){ continue; }
    if(action < 0){
      elms[i].checked = elms[i].checked ? 0 : 1;
    } else {
      elms[i].checked = action;
    }
  }
}

如果这令人困惑,请记住 checked 属性是一个布尔值。这意味着当它为 true 或 1 时,复选框被选中,当它为 false 或 0 时,复选框不被选中。如果只使用 true 或 false 关键字,则必须在 else 条件中添加另一个 case(在本例中通过三元符号):

function changeBoxes(action) {
  var f = document.forms[0];
  var elms = f.elements;
  for(var i = 0; i < elms.length; i++){
    if(elms[i].type != 'checkbox'){ continue; }
    if(action < 0){
      elms[i].checked = elms[i].checked ? false : true;
    } else {
      elms[i].checked = action == 1 ? true : false;
    }
  }
}

使用三元运算符,您甚至可以将脚本的整个复选框逻辑部分缩减为一行:

function changeBoxes(action) {
  var f = document.forms[0];
  var elms = f.elements;
  for(var i = 0; i < elms.length; i++){
    if(elms[i].type != 'checkbox'){ continue; }
    elms[i].checked = action < 0 ? (elms[i].checked ? 0 : 1) :action;
  }
}

因为许多复杂的表单代码不一定是由面向客户端的开发人员创建的,所以您很有可能会遇到这种类型的构造,这也是我在这里向您展示它的原因。

单选按钮

如果你想知道的话,单选按钮之所以这么叫是因为它们看起来像老式收音机上的转盘。它们就像复选框一样,不同之处在于它们属于一个同名的组,用户只能选择一个。对于鼠标和键盘用户来说,单选按钮非常容易使用,如果您在使用选择框时遇到问题,它们是短选择框的很好的替代品。

它们与复选框具有相同的布尔 Checked 和 defaultChecked 属性,但是当您设置一个复选框时,它们会自动将其他选项的 checked 属性设置为 false。同样,您可以读写 checked 和 defaultChecked,并且您需要重置表单以使对 defaultChecked 的更改直观地出现。因为示例 HTML 只有两个选项的单选按钮组,所以让我们使用一个不同的示例:

exampleFormRadioGroup.html(节选)

<form method="post" action="send.php">
  <fieldset>
    <legend>Step 1 of 3 - Your favourite Character </legend>
     <p>
       <input type="radio" name="character" id="charC" value="Calvin" checked="checked">
       <label for="charC">Calvin</label>
     </p>
     <p>
       <input type="radio" name="character" id="charH" value="Hobbes">
       <label for="charH">Hobbes</label>
   </p>
   <p>
      <input type="radio" name="character" id="charSd" value="Susie Derkins">
      <label for="charSd">Susie Derkins</label>
   </p>
   <p>
     <input type="radio" name="character" id="charS" value="Spaceman Spiff">
     <label for="charS">Spaceman Spiff</label>
   </p>
   <p>
     <input type="radio" name="character" id="charSm" value="Stupendous Man">
     <label for="charSm">Stupendous Man</label>
   </p>
  </fieldset>
  <p class="submit"><input type="submit" value="Next Step"></p>
</form>

image 注意这也是展示姓名和 id 区别的好机会。尽管一组单选按钮都有相同的名称(在本例中是 character),但是它们每个都必须有一个惟一的 id,这样标签才能与它们连接起来。标签不仅对于屏幕阅读器这样的辅助技术来说很方便,而且它们还使表单更容易使用,因为用户可以单击复选框旁边的名称来选择它们。

演示 HTML 包括一些显示 JavaScript 输出的按钮;您可以通过在浏览器中打开它来测试它。该脚本展示了如何处理单选按钮:

formRadioGroup.js

function setChoice(n) {
  var f = document.forms[0];
  f.character[n].checked = true;
}
function getChoice() {
  var f = document.forms[0];
  var choices = f.elements.character;
  for(var i = 0; i < choices.length; i++){
    if(choices[i].checked){ break; }
  }
  alert('Favourite Character is: ' + choices[i].value);
}

您可以将单选按钮组作为具有共享名称(在本例中为 character)的数组来访问。设置单选按钮组的选项非常简单:setChoice()函数将一个数字作为参数(n),读取第一个表单(forms[0]),并将第 n 个字符项的 checked 属性设置为 true。

formRadioGroup.js(节选)

function setChoice(n) {
  var f = document.forms[0];
  f.character[n].checked = true;
}

如果你点击例子中的 Set Choice To Hobbes 按钮,你会看到高亮显示的单选按钮发生变化,如图图 7-7 所示。

9781430250920_Fig07-07.jpg

图 7-7 。更改单选按钮组中的选定选项

读取当前选中的选项也很简单:选择第一种形式,将字符列表存储在一个名为 choices 的新变量中,然后遍历它。然后测试数组中每个元素的 checked 属性,当找到一个返回 true 的元素时,中断循环。这是当前选定的单选按钮:在任何同名的单选按钮组中只能选择一个。

formRadioGroup.js(续)

function getChoice() {
  var f = document.forms[0];
  var choices = f.elements.character;
  for(var i = 0; i < choices.length; i++) {
    if(choices[i].checked){ break; }
  }
  alert('Favourite Character is: ' + choices[i].value);
}

小跟班

HTML 中有三种按钮:两种不需要脚本就能工作,一种包含在规范中,只与脚本一起工作。

作者可以创建三种类型的按钮:

提交按钮:当被激活时,提交按钮提交一个表单。一个表单可以包含多个提交按钮。

复位按钮:当被激活时,复位按钮将所有控件复位到初始值。

按钮 : 按钮没有默认行为。每个按钮可能有与元素的事件属性相关联的客户端脚本。当事件发生时(例如,用户按下按钮、释放按钮等)。),关联的脚本被触发。

www.w3.org/TR/REC-html40/interact/forms.html#buttons

这个使得“按钮”——既可以是输入类型的按钮,也可以是按钮元素——成为纯 JavaScript 功能的完美触发元素。

另一方面,重置和提交按钮是表单中非常重要的部分,除非你有充分的理由,否则不应该被篡改。一个重复出现的请求是在提交表单时更改提交按钮的值或状态,以防止不耐烦的用户两次单击按钮。您可以通过点击处理程序来实现这一点;但是,更好的选择是在表单上使用提交处理程序,因为当通过 Enter 键提交表单时,这也会触发更改。图 7-8 显示了这可能是什么样子。

9781430250920_Fig07-08.jpg

图 7-8 。发送表单时更改提交按钮的样式和文本内容

要实现这一功能,您只需为窗口分配一个事件处理程序,在提交表单时调用 init()函数和另一个调用 change()函数。

这个函数遍历所有的表单元素(通过 getTarget()检索表单后),并检查元素是图像还是提交按钮。如果是这种情况,它通过 disabled 属性禁用按钮,并将按钮值更改为 Please wait:

examplechangesmitbutton . js

submitChange = {
  init : function() {
    DOMhelp.addEvent(document.forms[0], 'submit', submitChange.change,false);
  },
  change : function(e){
    var t = DOMhelp.getTarget(e);
    for(var i = 0; i < t.elements.length; i++){
      if(!/submit|image/.test(t.elements[i].type)) { continue; }
        t.elements[i].disabled = true;
        t.elements[i].value = 'Please wait... ';
     }
  }
}
DOMhelp.addEvent(window, 'load', submitChange.init ,false);

通过定义的图像按钮的行为类似于提交按钮,唯一的区别是它没有向后端提交其名称,而是提交两组名称-值对,由原始名称和后面的。x 和。y 和用户单击的坐标作为值。这样,您可以根据按钮被单击的位置执行不同的操作。这些信息不能通过 JavaScript 读取,只能在后端读取。

从 JavaScript 的角度来看,除了可以为图像输入提供翻转状态之外,您对图像输入没有什么可做的了。

选择框

选择框可能是最复杂和最通用的表单元素。设计师喜欢它们,因为它们可以使用选择框在一个小屏幕空间中存储大量选项供用户选择。

每个选择框都有一个名为 options 的列表对象,该对象有几个属性:

  • 长度:该选择框中所有选项的数量。
  • selected:如果用户选择了该选项,则为布尔值。
  • selectedIndex:所选元素的索引号。如果没有选择任何元素,则返回–1(这实际上是 SELECT 元素的一个属性,但适合在这里提及)。
  • 文本:选项的文本内容。
  • 值:选项的值。

image 注意注意文本和值是包含在选择框中的每个选项的属性;您不能通过读取选择框对象本身的 value 属性来读出所选的值,因为根本没有这样的东西。

有两种选择框:单选选择框允许一个独占选择,多选选择框允许用户通过按住 Ctrl 并突出显示所需选项来选择多个选项。

image 注意对于使用辅助技术或键盘的用户来说,多选选择框是一场噩梦,这就是为什么你可能要考虑使用复选框列表的原因。这也将使在服务器端读出选择变得更加容易。

读出单项选择框是相当容易的。例如,以演示表单中的选择框为例:

exampleSelectChoice.html(节选)

<p>
  <label for="subject">Subject</label>
  <select id="subject" name="subject">
    <option value="generalEnquiry" selected="selected">General question</option>
    <option value="Webdesign">Webdesign</option>
    <option value="Hosting">Hosting</option>
    <option value="Training">Training</option>
    <option value="Partnership">Partnership</option>
    <option value="other">Other</option>
  </select>
</p>

到达选择框的最快方法是使用元素的名称而不是索引。原因是选择框的元素类型可以是单选或多选,这取决于是否设置了 multiple 属性。一旦找到正确的对象,就可以使用它的 selectedIndex 属性来读取所选的选项,并通过将 selectedIndex 用作列表计数器来显示选项的值或文本:

考试选择题. js (excerpt)

function checkSingle() {
  var f = document.forms[0];
  var selectBox = f.elements['subject'];
  var choice = selectBox.selectedIndex;
  alert('You chose ' + selectBox.options[choice].text)
}

对于多选选择框,这还不够,因为用户可能选择了多个选项(selectedIndex 将只返回第一个选项)。

不使用 selectedIndex,您必须遍历所有选项并测试每个选项的 selected 属性:

考试选择题. js (excerpt)

function checkMultiple() {
  var f = document.forms[0];
  var selectBox = f.elements['multisubject'];
  var choices=[];
  for(var i = 0; i < selectBox.options.length; i++) {
    if(selectBox.options[i].selected == 1) {
      choices.push(selectBox.options[i].text);
    }
  }
  alert(choices.join(', '));
}

您可以通过 elements 集合中选择框的名称来访问它,并创建一个名为 choices 的新数组。([]是新数组()的快捷表示法。)遍历选择框的每个选项,并检查其 selected 属性是否为 true。在这种情况下,将选项的文本值作为新的数组项推入 choices。然后使用数组的 join()方法将数组转换为字符串并显示它。

这种读取值的方式也适用于单选选择框;然而,根据可用选项的数量,这可能是多余的。根据元素的类型,通过读出选项,可以将这两种方法放在一个更通用的函数中:

考试选择题. js (excerpt)

function getSelectValue(fieldName) {
  var f = document.forms[0];
  var selectBox = f.elements[fieldName];
  if(selectBox.type == 'select-one') {
    var choice = selectBox.selectedIndex;
    alert('You chose ' + selectBox.options[choice].text);
  } else {
    var choices = [];
    for(var i = 0;i < selectBox.options.length; i++){
      if(selectBox.options[i].selected == 1) {
        choices.push(selectBox.options[i].text);
      }
    }
    choices.join(', ');
    alert(choices);
  }
}

在选择框中添加选项

就表单元素而言,选择框是唯一的,因为您可以使用它们以编程方式添加或删除选项。通过使用选项构造函数并将其包含在选项列表中,可以添加新选项:

extraOption = new Option(value, text, defaultSelected, selected);

例如,如果您想将“DOM scripting”作为一个主题添加到列表中,您可以这样做:

考试选择题. js (excerpt)

function addOption(fieldName) {
  var f = document.forms[0];
  var selectBox = f.elements[fieldName];
  var extraOption = new Option('DOM scripting', 'domscripting', 0, 0);
  selectBox.options[ selectBox.options.length ] = extraOption;
}

删除和替换选择框中的选项

您可以通过将选项设置为空来删除它:

考试选择题. js (excerpt)

function removeOption(fieldName,i) {
  var f = document.forms[0];
  var selectBox = f.elements[fieldName];
  selectBox.options[i] = null;
}

替换选项也一样容易;只需将旧选项设置为新选项:

考试选择题. js (excerpt)

function replaceOption(fieldName, i) {
  var f = document.forms[0];
  var selectBox = f.elements[fieldName];
  var extraOption = new Option('DOM scripting', 'domscripting', 0 ,0);
  selectBox.options[i] = extraOption;
}

在另一个选项之前插入一个选项会有一些问题,因为在重写 options 集合之前需要复制所有的选项。函数 insertBeforeOption()接受两个参数:表单元素的名称和要在前面插入新选项的选项的索引。首先定义两个循环计数器 I 和 j,以及一个空白数组 opts,然后找到选择框并创建新选项。

考试选择题. js (excerpt)

function insertBeforeOption(fieldName, n) {
  var i = 0, j = 0, opts = [],
  var f = document.forms[0];
  var selectBox = f.elements[fieldName];
  var extraOption = new Option('DOM scripting', 'domscripting', 0,0);

然后将选择框中的选项存储在一个名为 old 的变量中,并遍历它们,为每个选项创建一个新选项,并将它们的属性分配给新选项。

示例 SelectChoice.js(续)

var old = selectBox.options;
for(i = 0; i < old.length; i++) {
  opts[i] = new Option(old[i].text, old[i].value, old[i].defaultSelected, old[i].selected);
}

新列表将增加一个元素,这就是为什么在遍历新列表之前增加 length 属性的原因。您测试循环计数器是否与发送给函数的参数相同,如果相同,则插入新选项。

示例 SelectChoice.js(续)

old.length++;
for(i = 0; i < old.length; i++) {
  if(i == n) {
    old[i] = extraOption;

否则,将选项设置为旧选项,并增加 j 计数器变量。注意这里需要第二个计数器,因为在循环过程中不能改变变量 I。因为新的选项列表将增加一项,所以您需要使用 j 来获取存储在 opts 数组中的值。

示例 SelectChoice.js(续)

    } else {
      old[i] = opts[j];
      j++;
    }
  }
}

根据选择框中选项的数量,这可能会成为一个相当慢且要求很高的脚本。通过使用 DOM,您可以用更少的代码更快地达到同样的效果:

考试选择题. js (excerpt)

function insertBeforeOptionDOM(fieldName, i) {
  var selectBox = document.getElementById(fieldName);
  if(!selectBox){ return false; }
  var opt = selectBox.getElementsByTagName('option');
  var extraOption = document.createElement('option');
  extraOption.setAttribute('value', 'domscripting');
  extraOption.appendChild(document.createTextNode('DOM Scripting'));
  selectBox.insertBefore(extraOption, selectBox.options[i]);
}

选择框是 web 应用开发的重要组成部分,传统上是通过来回移动元素来排序两个列表的界面。

交互式表单:隐藏和显示依赖元素

JavaScript 和表单的一个很酷的地方是,你可以让表单比开箱即用时更吸引人,更有活力。让所有东西都相互交互并立即发送一个表单,而不需要用户点击提交按钮或按回车键,这很有诱惑力。这样做的危险不仅在于你牺牲了对除可视代理之外的用户代理的支持,而且用户可能会过早地发送数据。

当要简单地改变界面或表单中显示的选项数量时,使用更改处理程序是相当安全的。让我们以演示表单为例。您可能已经注意到,有些字段有逻辑联系:“other subject”文本字段只有在选择了 other 选项时才有意义,只有当用户选择订阅时事通讯时,选择接收 HTML 或纯文本形式的时事通讯才起作用。

exampleDynamicForm.html(节选)

<form method="post" action="send.php">
  [... code snipped ...]
  <p><label for="subject">Subject</label>
  <select id="subject" name="subject">
    <option value="generalEnquiry" selected="selected">General question</option>
    <option value="Webdesign">Webdesign</option>
    <option value="Hosting">Hosting</option>
    <option value="Training">Training</option>
    <option value="Partnership">Partnership</option>
    <option value="other">Other</option>
  </select></p>
  <p><label for="otherSubject">specify other subject</label>
  <input type="text" id="otherSubject" name="otherSubject"></p>
  [... code snipped ...]
  <p><input type="checkbox" name="newsletter" value="yes" id="newsletter">
  <label for="newsletter">Sign me up for the newsletter</label></p>
  <p>Newsletter format:
  <input type="radio" name="newsletterFormat" id="newsHtml" value="html" checked="checked">
  <label for="newsHTML">HTML</label>
  <input type="radio" name="newsletterFormat" id="newsPlain"  value="plain">
  <label for="newsPlain">Text</label></p>

使用脚本,您可以隐藏这些选项,并使它们仅在用户选择适当的选项时才显示。图 7-9 显示了它在浏览器中的样子。

9781430250920_Fig07-09.jpg

图 7-9 。基于用户选择显示和隐藏表单元素

您可以定义一个类,应用于您想要隐藏的元素和两个动态元素的 id,作为名为 df 的主对象的属性。

dynamic form . js

df = {
  hideClass : 'hide',
  letterOption : 'newsletter',
  subjectOption : 'subject',

init()方法检查 DOM 支持以及必要的元素是否可用。

dynamicForm.js(续)

init : function() {
  if(!document.getElementById || !document.createTextNode){
   return;
  }
  df.news = document.getElementById(df.letterOption);
  df.subject = document.getElementById(df.subjectOption);
  if(!df.subject || !df.news){ return; }

接下来,您需要找到要隐藏的元素。通过使用 DOMhelp 方法 closestSibling(),您可以确保不要试图隐藏换行符,而是隐藏您实际想要到达的元素。将元素存储在主对象的属性中,以便事件处理程序方法可以访问它们。

您可以通过向元素添加 hiding 类来隐藏元素,并向名为 letterChange()的复选框分配 click 事件处理程序,向名为 subject Change()dynamic form . js 的选择框分配 change 处理程序(续)

  df.newsOpt = DOMhelp.closestSibling(df.news.parentNode, 1);
  df.subjectOpt = DOMhelp.closestSibling(df.subject.parentNode, 1);
  DOMhelp.cssjs('add', df.newsOpt, df.hideClass);
  DOMhelp.cssjs('add', df.subjectOpt, df.hideClass);
  DOMhelp.addEvent(df.news, 'click', df.letterChange, false);
  DOMhelp.addEvent(df.subject, 'change', df.subjectChange, false);
},

在测试复选框的 checked 属性之前,通过 letterChange()方法中的 getTarget()检索复选框。如果选中该属性,则移除隐藏类;否则,你添加它。

dynamicForm.js(续)

letterChange : function(e){
  var t = DOMhelp.getTarget(e);
  var action = t.checked ? 'remove' : 'add';
  DOMhelp.cssjs(action, df.newsOpt, df.hideClass);
},

subjectChange()方法的工作方式相同:检索目标并检查第五个选项是否是选中的选项(即 selectedIndex 是否等于 4)。如果是,就从可选元素中移除隐藏类;否则,您添加它。另外,该方法将浏览器的焦点设置为新显示的元素,以便用户可以立即开始键入。

dynamicForm.js(续)

  subjectChange : function(e) {
    var t = DOMhelp.getTarget(e);
    var action = t.selectedIndex == 5 ? 'remove' : 'add';
    DOMhelp.cssjs(action, df.subjectOpt, df.hideClass);
    if(action == 'remove') {
      df.subjectOpt.getElementsByTagName('input')[0].focus();
    }
  }
}
DOMhelp.addEvent(window, 'load', df.init, false);

显示和隐藏连接的元素是将部分表单连接到其他选项的一种方式。另一种方法是保持它们可见,但添加一个禁用的属性。这使得用户无法对它们进行更改,并且浏览器将它们显示为灰色。

这比隐藏元素的功能要弱一些,因为 disabled 属性只适用于 input、textarea、select、option、optgroup 和 button。图 7-10 显示了在 Windows 上的 Firefox 中禁用元素后的表单外观。

9781430250920_Fig07-10.jpg

图 7-10 。禁用元素而不是隐藏它们

该脚本的主要区别在于,您必须针对每个想要单独禁用的输入元素。在单选按钮的情况下,这意味着您必须经历一个循环。脚本中的更改以粗体突出显示,应该是不言自明的:

dynamicFormDisable.js

df = {
  hideClass : 'hide',
  letterOption : 'newsletter',
  subjectOption : 'subject',
  init : function() {
    if(!document.getElementById || !document.createTextNode){
     return;
    }
    df.news = document.getElementById(df.letterOption);
    df.subject = document.getElementById(df.subjectOption);
    if(!df.subject || !df.news){ return; }
    df.newsOpt = DOMhelp.closestSibling(df.news.parentNode, 1);
    df.newsOpt = df.newsOpt.getElementsByTagName('input');
    for(var i = 0; i < df.newsOpt.length; i++){
      df.newsOpt[i].disabled = 1;
    }
    df.subjectOpt = DOMhelp.closestSibling(df.subject.parentNode, 1);
    df.subjectOpt = df.subjectOpt.getElementsByTagName('input')[0];
    df.subjectOpt.disabled = 1;
    DOMhelp.addEvent(df.news, 'click', df.letterChange, false);
    DOMhelp.addEvent(df.subject, 'change', df.subjectChange, false);
  },
  letterChange : function(e){
    var i;
    var t = DOMhelp.getTarget(e);
    var disable = t.checked ? false: true ;
    for(i = 0; i < df.newsOpt.length; i++) {
      df.newsOpt[i].disabled = disable;
    }
  },
  subjectChange : function(e){
    var t = DOMhelp.getTarget(e);
    if(t.selectedIndex == 5) {
      df.subjectOpt.disabled = null;
      df.subjectOpt.focus();
    } else {
      df.subjectOpt.disabled = 1;
    }
  }
}
DOMhelp.addEvent(window, 'load', df.init, false);

使用 disabled 的实际结果是,这些元素也不能再通过 tab 键来访问——这对于隐藏的元素仍然是可能的(除非您通过将 display 设置为 none 来隐藏它们,如前面的站点导航部分所示)。

自定义表单元素

如果有足够的技能和测试时间,您可以使用 JavaScript 来扩展浏览器提供的常规表单控件,从而为用户提供您自己的自定义控件,甚至使它们可以通过键盘访问。特别是在 web 应用开发中,这可能是一个真正的必要性。

HTML5 增加了新的输入类型,赋予表单更多的功能。其中一些类型包括电话、搜索和电子邮件。对于 input、select 和 textarea 标签的 required 属性等选项,验证也变得更好了。

表单和 JavaScript 概述

我希望这一章能让你对表单和 JavaScript 有所了解。您了解了表单本身的不同属性和方法,以及它们各自的属性和方法可能包含的每个元素。您详细了解了如何处理选择框,以及如何通过隐藏依赖于其他元素的元素并仅在其他元素被激活或具有正确值时才显示它们,来使表单更加动态。

关于表单和 JavaScript,需要记住的主要内容是:

  • 尽量不要过度使用表单。一旦你完成了调整,看看这个表单是否还能用键盘。特别是,更长的表单更有可能通过从一个字段跳到另一个字段来填写,而不是单击不同的元素然后编辑它们。
  • 不要使用事件处理程序自动提交表单,可以通过让用户单击提交按钮或按 Enter 键来提交表单。不要剥夺用户的这些选择。
  • 虽然旧的表单集合表单和元素不是最新的 DOM 脚本技术(因为它们依赖于 HTML,而所有其他 DOM 方法也可以应用于 XML 字符串),但它们可能是在通用或生成的表单上使用的更容易的选项,因为您无法控制 id 或元素的数量。遍历一个元素列表比遍历一个表单的所有子元素并将它们与可能的元素名称进行比较,或者逐个遍历 input、textarea 和 select 元素集合要容易得多。

摘要

您现在应该能够处理 JavaScript 的最常见用法了。当你需要回忆如何处理图像、窗口、导航和表单时,你可以回到本章和上一章。

在下一章,我们将离开浏览器和客户端脚本的世界,专注于如何让 JavaScript 与后端和服务器端脚本对话。这也将使您能够了解 Ajax。