在这期专栏中,我将说明如何翻新选项卡式面板使其能够在不支持标准的4.0版本的老浏览器中工作,我还会稍微地改善面板的界面使其能够更具吸引力。

在翻新原来的选项卡式面板时,选项卡和面板在 Navigator 4.0中将会缺少边界,但是其它功能在 Netscape Navigator 或 Microsoft Internet Explorer 中都会一样。点击一个选项卡将显示与其关联的面板;点击后端的一个选项卡可以将引起选项卡的行移至前端。这就是我前面提到的一点小的界面改进;它使得所有行中的面板表现为同一种方式,而且使组件的行为更加一致。不幸的是,它就使得该组件不能再在 Opera 上运行,因为Opera 不支持 CSS clip属性。

另外一个显示上的改进是该组件不再是 DOM 专用,而且现在能在4.0版本的浏览器中运行。Internet Explorer 4.0 有相当健壮的 CSS 支持,因此只需要很少的改动。而 Navigator 4.0 的情况则不同。

针对 Navigator 4.0 改写
使选项卡式面板能够在 Navigator 4.0 下工作需要抹去 Netscape 的不好的layer标记,这是他们在 DHTML 上的一个倒退。浏览器允许一些 DHTML 使用与其它浏览器相同的div元素,但是在制作选项卡式面板上却有太多的限制。如果你想要在Navigator 4.0 中实现内嵌的、能够调整位置的、固定大小的、实色的区域,那么只能使用layer,否则就没有办法。

但是在改写之前,layer 也有其优点。对于我们的选项卡式面板,几行代码将Navigator 4.0 代码与其它代码分离:

<NOLAYER>
<!-- DIV tags used by other browsers -->
</NOLAYER>
<LAYER id="p1" width="400" height="200" src="nav4.html">
</LAYER>

layer 标记的src属性从文件nav4.html 导入Navigator 4.0 版本的选项卡式面板。nolayer 元素包含所有由其它浏览器使用的 div 元素。Navigator 4.0 将处理layer 标记并忽略多于nolayer 中的所有东西,而所有其它浏览器(包括Navigator 6.0)将忽略layer 和 nolayer。这样我们就清楚地将Navigator 4.0 系统从其它基于 CSS 的代码中分离出来。

在 nav4.html 文件中,我们有两组layer 标记,其中一组用于选项卡,另外一组用于面板。下面是一个选项卡的例子:

<LAYER bgcolor="yellow" style="font-weight:bold;text-align:center;font-family:sans-serif;" width="90" height="78" left="0" top="24" z-index="8" id="p1tab0" onfocus="selectTab(0)">
HTML
</LAYER>

注意style属性只对其包含的文本有效。layer本身是绝对定位的(ilayer 用于相对定位),它使用 HTML 属性来表示大小、布置、布局和背景色。事件处理函数onfocus对应鼠标点击。

面板与选项卡类型,但是内容稍多:

<LAYER bgcolor="yellow" style="font-family:sans-serif;padding:6px;" width="400" height="200" left="0" top="54" z-index="8" id="p1panel0">
<H2>Hypertext Markup Language</H2>
<P><A href="http://www.w3.org/MarkUp/" target="external">HTML</A> is the language in which Web pages are written. HTML uses tags to identify how text is to be structured and formatted within a document. </P>
</LAYER>

注意在style属性中指定了padding。这是在 Navigator 4.0 layer 中得到填充的唯一方法;如果你尝试使用样式表,那么边距只会产生稍大一点的边距。

对于Navigator 4.0,我们面临的最后一个问题是它不能不表(table)内表现绝对定位的内容。但是它可以在表上定位内容。所以我们首先要将导入nav4.html 的layer 移到 body 的最后,放在所有表之外。然后我们在需要面板的地方放置一个名为panelLocator的同样大小的空 ilayer。

<ILAYER id="panelLocator" width=400 height=254></ILAYER>

最后,我们在ilayer 的顶部添加一段脚本来定位选项卡式面板layer:

function positionPanel() {
document.p1.top=document.panelLocator.pageY;
document.p1.left=document.panelLocator.pageX;
}
if (document.layers) window.onload=positionPanel;

这个改写有个缺点,它需要相同内容的两个独立的拷贝,但是这是为提高效率的一个折衷。有时候可能需要在同一个页面的div中内嵌layer 来引用相同的内容,但是那样会使情况变得很复杂。很多忽略layer 的浏览器将会应用它的样式属性,并且在这种情况下,浏览器就必须从页面的最后端重新定位选项卡式面板。他们可以实现这个,但是会增加复杂性以及潜在的不稳定性。因此最好为Navigator 4.0 有问题的 DHTML 保留两个拷贝。

增强选项卡式面板
在 Navigator 4.0 特殊的区域之外,HTML 和用于选项卡式面板的脚本与上一个专栏的相同。ilayer 标记内部是一个相对定位的 div 元素。

<DIV id="p1" style="background-color:transparent; position:relative; height:254px; width:400px; padding:6px;">

注意padding属性,即使是最新的浏览器在对待padding 上也有区别,有的将其表现在 width 之内,有的将其表现在 width 之外,所以它包含在这里是为了保证这个div 分配到足够的空间。这个 div 包含两组绝对定位的 div 构成选项卡和面板。这与它们对应的 layer 相似,但是其外观却完全地来自于样式,并且使用onclick捕获点击事件:

<DIV class="tab" style="background-color:yellow; left: 0px; top: 24px; z-index:8" id="p1tab0" onclick="selectTab(0)">
HTML
</DIV>

选项卡和面板div 使用class 属性从页面head 部分的一个 style 元素导入公共样式属性。你可以为Navigator 4.0 的layer 添加样式规则,但是它们必须应用到选项卡和面板两者,因为Navigator 4.0 在layer 中不支持class。

脚本被修改为支持行交换以及非DOM 浏览器。对于行交换,它添加divLocatoin和newLocation数组来跟踪选项卡的位置:

vardivLocation = new Array(numLocations)
varnewLocation = new Array(numLocations)

for(vari=0; i<numLocations; ++i) {
divLocation[i] = i
newLocation[i] = i
}

函数updatePosition() 使用这些数组根据一个选项卡是否被点击并显示在前端而定位选项卡。

function updatePosition(div, newPos) {
newClip=tabHeight*(Math.floor(newPos/tabsPerRow)+1)
if (document.layers) {
div.style=div;
div.clip.bottom=newClip; // clip off bottom
} else {
div.style.clip="rect(0 auto "+newClip+" 0)"
}
div.style.top = (numRows-(Math.floor(newPos/tabsPerRow) + 1)) * (tabHeight-vOffset)
div.style.left = (newPos % tabsPerRow) * tabWidth + (hOffset * (Math.floor(newPos / tabsPerRow)))
}

注意函数updatePosition()在调整选项卡 clip 属性时考虑到了Navigator 4.0(通过document.layers)。因为在选项卡移动到一个新行时其高度发生变化,所以clip 属性被调整。

为了处理不同的对象模型,getDiv() 函数根据浏览器返回一个与某个选项卡或面板的 id 匹配的对象的引用。setZIndex() 函数使用getDiv() 调整一个对象的z-index。

function getDiv(s,i) {
var div
if (document.layers) {
div = document.layers[panelID].layers[panelID+s+i]
} else if (document.all && !document.getElementById) {
div = document.all[panelID+s+i]
} else {
div = document.getElementById(panelID+s+i)
}
return div
}

function setZIndex(div, zIndex) {
if (document.layers) div.style = div;
div.style.zIndex = zIndex
}

最后当一个选项卡被点击时,selectTab() 函数使用getDiv() 和setZIndex() 重新排序选项卡和面板。selectTab() 也使用updatePosition() 将选项卡移动到适当的行。

function selectTab(n) {
// n is the ID of the division that was clicked
// firstTab is the location of the first tab in the selected row
varfirstTab = Math.floor(divLocation[n] / tabsPerRow) * tabsPerRow
// newLoc is its new location
for(vari=0; i<numDiv; ++i) {
// loc is the current location of the tab
var loc = divLocation[i]
// If in the selected row
if(loc >= firstTab && loc < (firstTab + tabsPerRow)) newLocation[i] = (loc - firstTab)
else if(loc < tabsPerRow) newLocation[i] = firstTab+(loc % tabsPerRow)
else newLocation[i] = loc
}
// Set tab positions & zIndex, update location
for(vari=0; i<numDiv; ++i) {
var loc = newLocation[i]
var div = getDiv("panel",i)
if(i == n) setZIndex(div, numLocations +1)
else setZIndex(div, numLocations - loc)
divLocation[i] = loc
div = getDiv("tab",i)
updatePosition(div, loc)
if(i == n) setZIndex(div, numLocations +1)
else setZIndex(div,numLocations - loc)
}
}

改写该组件的一个不幸的意外是Opera,它不支持 clip 属性。clip 只在对选项卡和面板进行无缝连接时才有必要。如果你不在乎它们之间有一个边界,那么你可以修改selectTab(),首先设置z-index,这样就将选项卡放到面板之后。