Click here to Skip to main content
Licence CPOL
First Posted 30 Jun 2007
Views 31,291
Downloads 197
Bookmarked 37 times

Advanced AJAX ListBox Component v0.3

By | 30 Jun 2007 | Article
Enforcing browser compatibility for horizontal scrolling and scroll state preservation.

ListBoxComponent0.3.gif

Introduction

In my last article, we updated our ASP.NET AJAX-enabled ListBox control, and separated client scroll state preservation from horizontal scrolling, so that the two could be configured separately. In this article, we're going to build upon that code and further enforce cross-browser functionality.

Background

We've come a long way, but we still have more features to add to our ListBox control. We're going to keep the biggest ones on hold until the next article though, and I'll tell you why. The main problem we left off with in the last article was that scroll state is only correctly saved in IE6 when HorizontalScrollEnabled is set to true. This is because IE6 does not execute the ListBox's onscroll event and will always return 0 as the ListBox's scrollTop value. Furthermore, it still wouldn't work without those two issues because setting the ListBox's scrollTop in IE6 doesn't even do anything. As far as I'm concerned, it is unacceptable to require HorizontalScrollEnabled be true in order to achieve the intended functionality in as common a browser as IE6... especially since we can solve it with changes in the code.

We want to have as few "hacks" as possible, but at the same time, good programmers have to write hacks from time to time... and I personally feel a little better about them when they're justified. In this case, IE6's behavior differs so much from that of Firefox (and IE7, for that matter), I can justify writing a browser hack. The scrollTop property of our ListBox is completely impotent in IE6. But, scrollTop of our containing DIV is fully operational. We can achieve our desired results by using the DIV container's scrollbars for IE6 even when HorizontalScrollEnabled is false.

If You Can't Keep It in Your Pants, Keep It on the Server

What I cannot justify is any client-side browser-sniffing code. We can get consistent and useful browser information on the server from the Page's Request.Browser object. The tricky part is how to refactor the code so that the control is rendered properly in all of our target browsers according to the HorizontalScrollEnabled and ScrollStateEnabled properties. In the last article, we extracted the ScrollStateEnabled property out of the single HorizontalScrollEnabled setting we started with in version 0.1. What we have to do here isn't much different... we have to separate out a RequiresScrollContainer property from the HorizontalScrollEnabled property. Again, let's start by adding the property.

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;
    }
}

What we're saying here is that whether or not a ListBox requires scrolling to be handled by a DIV container depends on more than just the HorizontalScrollEnabled property. Sure, if HorizontalScrollEnabled is true, of course, we need horizontal scrolling to be handled by the DIV. But, even when it's false, we still need the DIV to handle scrolling in IE6 (and, as it turns out, Opera) when ScrollStateEnabled is true.

Using this new property, we can pretty easily refactor the rendering code. Instead of just delegating certain style properties to the container when HorizontalScrollEnabled is true, we now want to delegate them when RequiresContainerScroll is 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);
    }
}

Because Opera doesn't support overflow-x or overflow-y, it will display horizontal scrollbars even when HorizontalScrollEnabled is false. So that we don't have to deal with this issue, let's just pretend Opera's such a great, advanced browser, it shows horizontal scrollbars without our help :P

Pass the Server Control Property to the Client Control

Now, we need to tell the client script whether the control requires container scrolling. This part should be getting easy by now.

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;
    }
,

This is how we keep the browser hack on the server.

Use the Server Control Property in the Client Control

Now, we have to use this new property in the client code to register the correct events, initialize the UI, store the correct scroll state, and then restore it after postback. All this involves is replacing certain instances of this.get_horizontalScrollEnabled() with this.get_requiresContainerScroll(), like we did in the server code. Here's what it will look like:

    _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;
    }
,

...And there you have it. We can now support both IE6 and Opera (tested only in Opera 9.21 so far) when horizontal scrolling is disabled and scroll state preservation is enabled. Again, the only quirk is that Opera will show horizontal scroll bars even when horizontal scrolling is disabled. As far as I can tell, this is a necessity to support that browser at all. The good news is, if Opera ever decides to support the overflow-x and overflow-y CSS properties, this code should accommodate for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

danludwig



United States United States

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralViewing all articles in this series Pinmemberdanludwig6:31 9 Apr '08  
GeneralAJAX tabContainer versus custom listBox PinmemberCurtis1569:51 26 Mar '08  
GeneralRe: AJAX tabContainer versus custom listBox Pinmemberdanludwig6:41 9 Apr '08  
QuestionSlider on Slidebar component Pinmemberadanker6:22 21 Sep '07  
GeneralRe: Slider on Slidebar component Pinmemberdanludwig5:50 9 Apr '08  
GeneralWay of debugging PinmemberWray Smallwood13:03 5 Sep '07  
GeneralRe: Way of debugging Pinmemberdanludwig5:47 9 Apr '08  
GeneralAdded some code PinmemberWray Smallwood12:15 5 Sep '07  
GeneralRe: Added some code Pinmemberdanludwig12:36 5 Sep '07  
Questionno souce code?? Pinmemberkpdsouza20:20 17 Jul '07  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120517.1 | Last Updated 30 Jun 2007
Article Copyright 2007 by danludwig
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid