高级 AJAX 列表框组件 v0.6
关于一个可水平滚动的列表框组件的最终文章。
引言
在我上一篇文章中,我们添加了一个服务器控件属性,允许我们控制列表框如何响应鼠标滚轮事件。在这篇文章中,我们将处理一些异常情况,并完全强制跨浏览器兼容鼠标滚轮。
背景
再次,让我们回顾一下我们在第二篇文章中列出的需求清单。现在我们开始深入细节,我们将把第 4 项分解成两个独立的需求。
通过使用 SHIFT 和 CONTROL 键一次性选择多个不相邻的项是困难的或不可能的,因为它们的onkeydown
事件会触发滚动条根据所选的第一项/几项重新定位。我们应该读取事件的keyCode
,并为“元键”按下(如本例)跳过我们的滚动代码。通过鼠标滚轮滚动项的能力取决于浏览器和加载页面的版本。我们希望使鼠标滚动成为一个可配置的选项**支持所有目标浏览器**。只有当控件的HorizontalScrollEnabled
属性被明确设置为true
时,垂直和水平滚动条的位置才会在回发时得到保留。即使HorizontalScrollEnabled
为false
,我们也应该能够保留滚动状态。- 我们的事件处理策略不支持 Firefox 1.0,因为一些改变滚动位置的用户交互不会执行
_onContainerScroll
事件处理程序。 - 如果您尝试调试
_onContainerScroll
处理程序,您会注意到 IE 可以为单个鼠标单击或按键执行它多达四次。拖动滚动条会导致我的 CPU 短暂飙升到 30% 或更高,具体取决于滚动速度。多次更新隐藏字段会给客户端计算机的处理器带来不必要的负载。
虽然我们似乎已经解决了 #2 的问题,但那些热衷于 Firefox 的人可能会注意到以下怪癖。
- 在 FF1 中,当
HorizontalScrollEnabled
==true
且MouseWheelScroll
==Enforce
时,当鼠标光标位于滚动条上时,列表框不响应鼠标滚轮。 - 在 FF1.5 和 FF2 中,当
HorizontalScrollEnabled
==true
且MouseWheelScroll
==Prevent
时,当鼠标光标位于滚动条上时,列表框确实响应鼠标滚轮。 - 在 FF1 中,当使用鼠标滚轮滚动时,滚动状态未保存到隐藏字段。顺便说一句,这是要求 #4 中最后一个悬而未决的问题。
前两个怪癖的出现是因为我们没有处理 DIV
容器的 DOMMouseScroll
事件。我们应该首先解决这个问题,因为它最终会影响第三个。
复制和粘贴
这就是为什么我们在上一篇文章中给 DIV
设置了一个指向原型的 _thisPrototype
字段的原因。现在,当 DIV
接收到 onscroll
事件时,我们已经可以访问客户端控件中计算新滚动位置所需的字段。当然,我可以以不同于 _onMouseWheel
代码的方式编写 _onContainerMouseWheel
代码,因为 this
引用将始终指向 DIV
。但是,这样写可以让我们有机会稍后将代码合并到一个辅助函数中。
// find this snippet inside if _initializeEvents()
// FF doesn't have an onmousewheel event
if (this.get_element().onmousewheel === undefined)
{
this.get_element().addEventListener(
'DOMMouseScroll', this._onMouseWheel, false);
// register the container's wheel with a different handler
if (this.get_requiresContainerScroll())
{
this.get_elementContainer().addEventListener(
'DOMMouseScroll', this._onContainerMouseWheel, false);
}
}
// 3c)
// Define the event handlers
//
_onContainerMouseWheel : function(e)
{
var _this = this._thisPrototype;
// stop the mouse wheel from scrolling
if (_this.get_mouseWheelScroll() == false)
{
e.preventDefault();
return false;
}
// enforce mouse wheel scrolling
else if (_this.get_mouseWheelScroll() == true)
{
var listBox = _this.get_element();
var container = this;
var scrollingElement = this;
var direction = (e.detail > 1) ? 1 : -1;
// scroll the ListBox by the height of one item in the correct direction.
var stepSize = scrollingElement.scrollHeight / listBox.options.length;
var newScrollTop = scrollingElement.scrollTop + (stepSize * direction);
scrollingElement.scrollTop = newScrollTop;
// tell the browser we're taking care of the mouse wheel.
e.preventDefault();
return false;
}
}
,
我们所有目标 Firefox 版本现在都能完全符合控件的 MouseWheelScroll
设置,当光标位于滚动条上时。恭喜,我们现在可以勾选需求 #2 了!
冲刺阶段
既然那个很简单,让我们来做一些更有挑战性的事情。默认情况下,Firefox 1.0 在使用鼠标滚轮滚动时,不会为列表框或 DIV
容器触发 onscroll
事件。这意味着隐藏字段没有用最新的定位信息进行更新。我们可以强制它做到这一点,但首先,让我们检查一下有必要进行这种情况。
请记住,FF1 的默认鼠标滚轮行为是允许它在禁用水平滚动且列表框获得焦点时滚动列表框。这意味着即使 MouseWheelScroll
设置为 NotSet
(也称为 _mouseWheelScroll
== null
),我们也必须注册 _onMouseWheel
处理程序。
_initializeEvents : function()
{
// handle mouse wheel events separately from all others
if (this.get_mouseWheelScroll() != null)
{
// ...
}
// MouseWheelScroll == NotSet, hack FF1
else if (!this.get_requiresContainerScroll()
&& this.get_scrollStateEnabled()
&& this.get_element().onmousewheel === undefined)
{
this.get_element().addEventListener(
'DOMMouseScroll', this._onMouseWheel, false);
}
// ...
}
,
现在,在 _onMouseWheel
处理程序中,我们可以处理需要更新 Firefox 滚动状态的情况,当禁用水平滚动、启用滚动状态且 MouseWheelScroll
设置为 NotSet
时。
_onMouseWheel : function(e)
{
var _this = this._thisPrototype
if (_this === undefined)
_this = this;
// stop the mouse wheel from scrolling
if (_this.get_mouseWheelScroll() == false)
{
// ...
}
// enforce mouse wheel scrolling
else if (_this.get_mouseWheelScroll() == true)
{
// ...
}
// MouseWheelScroll == NotSet, hack FF1
else if (!_this.get_requiresContainerScroll()
&& _this.get_scrollStateEnabled()
&& _this.get_element().onmousewheel === undefined)
{
_this._updateScrollState();
}
}
,
我必须承认,我花了点时间才弄清楚为什么上面的代码不起作用。当我发现 this._thisPrototype
返回 undefined 时,我责怪了自己。我忘记了在 _initializeUI()
中,_thisPrototype
仅在 MouseWheelScroll
设置为 Enforce
或 Prevent
时才被定义。我们必须移除该函数中的条件检查,以便在 MouseWheelScroll
设置为 NotSet
时,我们能够访问 DIV
的 _thisPrototype
。但是,在 this.get_elementContainer()
返回 null
的情况下,我们无法向其添加字段。
_initializeUI : function()
{
var listBox = this.get_element();
var container = this.get_elementContainer();
// hack to support mouse wheel scrolling
listBox._thisPrototype = this;
if (container != null)
{
container._thisPrototype = this;
}
// ...
}
,
滑入
我们要让 FF1 做同样事情的另一个场景是当 MouseWheelScroll
等于 Enforce
时,但我们必须在 DIV
和列表框的鼠标滚轮处理程序中都这样做。必须在条件块的末尾更新隐藏字段,就在设置 scrollTop
属性之后,但在 return false
行之前。
_onContainerMouseWheel : function(e)
{
var _this = this._thisPrototype;
// stop the mouse wheel from scrolling
if (_this.get_mouseWheelScroll() == false)
{
// ...
}
// enforce mouse wheel scrolling
else if (_this.get_mouseWheelScroll() == true)
{
// ...
scrollingElement.scrollTop = newScrollTop;
// hack FF1 into saving scroll state
if (_this.get_scrollStateEnabled())
{
_this._updateScrollState();
}
// tell the browser we're taking care of the mouse wheel.
e.preventDefault();
return false;
}
}
,
_onMouseWheel : function(e)
{
var _this = this._thisPrototype
if (_this === undefined)
_this = this;
// stop the mouse wheel from scrolling
if (_this.get_mouseWheelScroll() == false)
{
// ...
}
// enforce mouse wheel scrolling
else if (_this.get_mouseWheelScroll() == true)
{
// ...
scrollingElement.scrollTop = newScrollTop;
// hack FF1 into saving scroll state
if (this._thisPrototype != undefined
&& _this.get_scrollStateEnabled())
{
_this._updateScrollState();
}
// tell the browser we're taking care of the mouse wheel.
e.preventDefault();
return false;
}
// MouseWheelScroll == NotSet, hack FF1
else if (!_this.get_requiresContainerScroll()
&& _this.get_scrollStateEnabled()
&& _this.get_element().onmousewheel === undefined)
{
// ...
}
}
,
……这是六篇文章以来,我们第一次可以说我们的列表框完全支持 Firefox 1.0。
全局概览
不,我不会写一篇关于如何处理第五个要求的文章。那会很无聊,而且无论代码目前多么低效,它至少都能展现出我们设定的行为。你们中的一些人可能正坐在那里,阅读这篇文章,翻阅源代码和演示,心想:“伙计,这个人为一个我可能只在我的一个或两个表单上使用的控件写了一千多行代码。”你们是对的。我的唯一回答是,如果你觉得这代码量很大,你应该看看 ASP.NET AJAX 控件工具包的源代码。我实际上添加了更多代码来满足我的生产版本的这个控件的第五个要求,但我将其留给你们自己去探索。
作为一名应用程序专家,我的首要任务是我创建的系统的用户。让他们的工作更轻松是我要做的事。然而,尽可能地,我也想让自己更轻松。ASP.NET WebControls 非常棒,尽管 ASP.NET 2.0 为验证器、按钮等添加了很多客户端功能,但它们仍然是首先的服务器控件。在 Web 应用程序中满足和超越用户需求需要我们超越服务器,进入他们最终使用的浏览器的客户端功能。
正如我在第一篇文章中所说,我是 Java 和 J2EE 认证的……但我必须赞扬微软的这个框架。将一个服务器控件与可配置的、跨浏览器的客户端功能集成,为快速改进 Web 应用程序用户界面打开了许多机会。我想学习更多关于它的知识,他们说学习某事最好的方法就是教给别人。希望您学到的和我一样多 :)