高级AJAX列表框组件 v0.3
强制浏览器兼容性以支持水平滚动和滚动状态的保存。
引言
在我上一篇文章中,我们更新了我们的ASP.NET AJAX启用的列表框控件,并将客户端滚动状态的保存与水平滚动分离开来,以便两者可以单独配置。在本文中,我们将在此代码的基础上进一步强制跨浏览器功能。
背景
我们已经走了很长一段路,但我们的列表框控件还有更多功能要添加。不过,最大的功能我们会留到下一篇文章再处理,我来告诉您为什么。上篇文章遗留的主要问题是,只有当HorizontalScrollEnabled
设置为true
时,滚动状态才能在IE6中正确保存。这是因为IE6不会执行列表框的onscroll
事件,并且始终返回0作为列表框的scrollTop
值。此外,即使解决了这两个问题,它仍然无法正常工作,因为在IE6中设置列表框的scrollTop
根本不起作用。就我个人而言,要求HorizontalScrollEnabled
为true
才能在像IE6这样常见的浏览器中实现预期功能是不可接受的……尤其是我们可以通过代码更改来解决它。
我们希望尽可能少地使用“ hack ”,但同时,优秀的程序员有时确实必须编写 hack……而我个人在它们有正当理由时感觉会好一些。在这种情况下,IE6的行为与Firefox(以及IE7)的行为差异很大,我可以为编写浏览器 hack 辩护。我们的列表框的scrollTop
属性在IE6中完全无效。但是,我们容器DIV
的scrollTop
却完全有效。即使HorizontalScrollEnabled
为false
,我们也可以通过使用IE6的DIV
容器的滚动条来实现我们想要的结果。
如果你忍不住,就把它放在服务器上
我无法辩护的是任何客户端浏览器嗅探代码。我们可以从Page
的Request.Browser
对象中获取服务器上一致且有用的浏览器信息。棘手的部分是如何重构代码,以便控件根据HorizontalScrollEnabled
和ScrollStateEnabled
属性在所有目标浏览器中正确呈现。在上一篇文章中,我们从0.1版本开始的单一HorizontalScrollEnabled
设置中提取了ScrollStateEnabled
属性。我们在这里要做的事情没有什么不同……我们需要从HorizontalScrollEnabled
属性中分离出一个RequiresScrollContainer
属性。同样,让我们先添加这个属性。
protected virtual bool RequiresContainerScroll
{
get
{
if (HorizontalScrollEnabled)
return true;
else if (ScrollStateEnabled
&& Page.Request.Browser.Browser.Equals("IE")
&& Page.Request.Browser.MajorVersion < 7)
return true;
// Opera exhibits the same behavior as IE6 when
// scrolling inside and outside of a DIV container
else if (ScrollStateEnabled
&& Page.Request.Browser.Browser.Equals("Opera"))
return true;
return false;
}
}
我们在这里的意思是,列表框是否需要由DIV
容器处理滚动,这不仅仅取决于HorizontalScrollEnabled
属性。当然,如果HorizontalScrollEnabled
为true
,那么我们当然需要由DIV
来处理水平滚动。但是,即使它为false
,当ScrollStateEnabled
为true
时,我们仍然需要DIV
来处理IE6(以及,结果证明,Opera)的滚动。
使用这个新属性,我们可以相当容易地重构渲染代码。我们不再仅仅在HorizontalScrollEnabled
为true
时将某些样式属性委托给容器,现在当RequiresContainerScroll
为true
时,我们希望将它们委托过去。
protected virtual void AddContainerAttributesToRender(HtmlTextWriter writer)
{
// the container now depends on 3 different property values
if (this.HorizontalScrollEnabled || this.ScrollStateEnabled)
{
// add required container attributes
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID
+ ContainerClientIdSuffix);
// add conditional container attributes
if (this.HorizontalScrollEnabled)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.Overflow, "auto");
}
else if (this.RequiresContainerScroll)
{
// Opera doesn't support overflow-x or overflow-y
writer.AddStyleAttribute(HtmlTextWriterStyle.Overflow, "auto");
writer.AddStyleAttribute(HtmlTextWriterStyle.OverflowX, "hidden");
}
if (this.RequiresContainerScroll)
{
writer.AddStyleAttribute(HtmlTextWriterStyle.Width,
this.Width.ToString());
// add other optional container attributes
// move style declarations from the Style attribute
// into the DIV container.
}
}
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
if (RequiresContainerScroll)
{
// the code inside this if block stays the same.
// only the condition is changed.
}
else
{
base.AddAttributesToRender(writer);
}
}
由于Opera不支持overflow-x
或overflow-y
,即使HorizontalScrollEnabled
为false
,它也会显示水平滚动条。为了避免处理这个问题,我们就假装Opera是一个很棒的、高级的浏览器,它在我们不帮助的情况下也能显示水平滚动条:P
将服务器控件属性传递给客户端控件
现在,我们需要告诉客户端脚本控件是否需要容器滚动。这部分现在应该很容易了。
protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
"DanLudwig.Controls.Client.ListBox", this.ClientID);
descriptor.AddProperty("requiresContainerScroll",
this.RequiresContainerScroll);
descriptor.AddProperty("scrollStateEnabled", this.ScrollStateEnabled);
descriptor.AddProperty("horizontalScrollEnabled",
this.HorizontalScrollEnabled);
descriptor.AddProperty("scrollTop", this.ScrollTop);
descriptor.AddProperty("scrollLeft", this.ScrollLeft);
return new ScriptDescriptor[] { descriptor };
}
// 2.)
// Define the client control's class
//
DanLudwig.Controls.Client.ListBox = function(element)
{
// initialize base (Sys.UI.Control)
DanLudwig.Controls.Client.ListBox.initializeBase(this, [element]);
// declare fields for use by properties
this._requiresContainerScroll = null;
this._scrollStateEnabled = null;
this._horizontalScrollEnabled = null;
this._scrollTop = null;
this._scrollLeft = null;
}
// 3d)
// Define the property get and set methods.
//
set_requiresContainerScroll : function(value)
{
if (this._requiresContainerScroll !== value)
{
this._requiresContainerScroll = value;
this.raisePropertyChanged('_requiresContainerScroll');
}
}
,
get_requiresContainerScroll : function()
{
return this._requiresContainerScroll;
}
,
这就是我们将浏览器 hack 保留在服务器端的方式。
在客户端控件中使用服务器控件属性
现在,我们必须在客户端代码中使用此新属性来注册正确的事件、初始化UI、存储正确的滚动状态,然后在回发后恢复它。这一切都涉及到将某些this.get_horizontalScrollEnabled()
的实例替换为this.get_requiresContainerScroll()
,就像我们在服务器代码中所做的那样。以下是它的样子
_initializeEvents : function()
{
// when horizontal scroll is enabled, use 3 zivros events
if (this.get_requiresContainerScroll())
{
// same code that previously fell under
// if (this.get_horizontalScrollEnabled())
}
// the rest of this method stays the same
}
,
_initializeUI : function()
{
var listBox = this.get_element();
var container = this.get_elementContainer();
if (this.get_requiresContainerScroll())
{
// same code that previously fell under
// if (this.get_horizontalScrollEnabled())
}
if (this.get_scrollStateEnabled())
{
this._restoreScrollState();
}
if (this.get_requiresContainerScroll())
{
// same code that previously fell under
// if (this.get_horizontalScrollEnabled())
}
}
,
_restoreScrollState : function()
{
var scrollingElement = this.get_elementContainer();
if (!this.get_requiresContainerScroll())
scrollingElement = this.get_element();
scrollingElement.scrollTop = this.get_scrollTop();
scrollingElement.scrollLeft = this.get_scrollLeft();
}
,
// return the client scroll state data that will go in the hidden field
get_scrollState : function()
{
var scrollingElement = this.get_elementContainer();
if (!this.get_requiresContainerScroll())
scrollingElement = this.get_element();
return scrollingElement.scrollTop + ':' + scrollingElement.scrollLeft;
}
,
……好了。现在,当禁用水平滚动并启用滚动状态保存时,我们可以同时支持IE6和Opera(到目前为止只在Opera 9.21上测试过)。同样,唯一的怪癖是Opera即使在禁用水平滚动时也会显示水平滚动条。据我所知,这是支持该浏览器所必需的。好消息是,如果Opera将来决定支持overflow-x
和overflow-y
CSS属性,此代码也应该能够适应。