Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Replacing IFrames with Panels under AJAX

4.25/5 (9 votes)
9 Mar 2009CPOL5 min read 90K  
How to maintain scrolltop in AJAX.

Introduction

As you know, the W3C standards group, in its infinite wisdom, has deprecated IFrames and their more powerful cousin, frames. There are reasons for this – having to do with breaking the basic paradigm of the page as the atomic unit of information – and arguments against it centering around code reuse. But, whether or not it was a good idea, it's been done, and we need to stay current.

The app I am working on would once have used IFrames to display content from other pages in the same app. But, deprecation leads sooner or later to non-support in the major browsers.

Background

After trying a number of possibilities, I settled on what I think is the best alternative to IFrames in ASP.NET. That is the Panel control. Using it is quite simple.

Step one is to place a Panel on the page where, in the past, an IFrame would have been used. Step two is to create a User Control. (See Chris Maunder's User Controls in ASP .NET, if you need an intro to User Controls.)

Step three is to place exactly the same content on the User Control that you would have put on the page being displayed in the IFrame. This approach has a number advantages including being able to view most of the content at design time and the relative ease with which you can use the properties and methods of the controls placed on the User Control.

Here's the code to display a User Control in a Panel:

ASP.NET
<asp:Panel ID="pnlOne" runat="server" 
           Height="340px" ScrollBars="Auto" 
           Style=" width: 728px; height: 340px;">
    <uc1:Engines ID="UserControlOne" runat="server" />
</asp:Panel>

Your can write the code, or you can drag the Panel from the tool box and then drag the User Control from the Solution Explorer - that way, VS will even register the control for you. Otherwise, you'll have to register it yourself.

This next step is optional, but can save you lots of time. You can easily access controls you have placed on the User Control by creating an alias like this:

VB
' Global Variables
Dim myFirstDiv As HtmlGenericControl
Dim mySecondDiv As HtmlGenericControl

Protected Sub Page_Load(ByVal sender As Object, _
                        ByVal e As System.EventArgs) Handles Me.Load
    myFirstDiv =  divMain.FindControl("pnlOne").FindControl(
                          "FirstDiv1").FindControl("divFirstDiv")
    mySecondDiv =  divMain.FindControl("pnlOne").FindControl(
                           "FirstDiv1").FindControl("divSecondDiv")

or for those who see sharpley :):

C#
// Global Variables
private HtmlGenericControl myFirstDiv;
private HtmlGenericControl mySecondDiv;protected void 
        Page_Load(object sender, System.EventArgs e)
{
   myFirstDiv = divMain.FindControl("pnlOne").FindControl(
                    "FirstDiv1").FindControl("divFirstDiv");
   mySecondDiv = divMain.FindControl("pnlOne").FindControl(
                    "FirstDiv1").FindControl("divSecondDiv");

Note: if you have been displaying pages from another site in your IFrame, then you'll need to write a screen scraper and put it in your User Control.

So far, so good. And, if the content of the User Control is larger than the dimensions of the Panel, its auto-scrollbars will come into play. Since controlling how much content takes up how much room on your page can become a major UI design consideration, life is sweet.

Using the Panel in AJAX

But, you want to use AJAX to make your app even sweeter?

Well, I think AJAX is the greatest thing to come down the pike since sliced bread. The power to request from the server and display on the client, a document, only once, and after that, only request parts of that document, is a powerful tool.

But -- there's always a but, isn't there?

The problem is that using Microsoft's AJAX and the UpdatePanel, along with Microsoft's browser, the only control that remembers its scroll top through an entire request cycle, is – you guessed it – the IFrame you just replaced. (And yes, FF doesn't have this problem – go figure.)

It seems that when the UpdatePanel refreshes its contents and rebuilds the portion of the DOM it has responsibility for, IE is unaware of this refresh and there is no way to get notifications from any part of an UpdatePanel after an update has occurred.

This means that no matter what you do, no matter how you try, every time there's a round trip to the server, those scrollbars are going to return to the top of the Panel and scrolltop will once again be 0. Now, if there's some kind of form being filled out here by the user, this will get really old, really fast.

Luckily, there's a very nice workaround.

Warning: JavaScript is about to be discussed: Warning

The methodology we're going to use is explained in great detail, almost mind-boggling detail on Microsoft's AJAX site. But in a nutshell, two of the client-side script libraries Microsoft has exposed via the Script Manager, i.e., Sys.WebForms.PageRequestManager beginRequest Event and Sys.WebForms.PageRequestManager endRequest Event, provide the ability for us to be notified just before the UpdatePanel charges off to the server to be rewritten, and just as soon as it has returned to the client.

By hooking into these two events, we can use a little script to put the scrolltop on any control (ASP or HTML) back where it was before the panel was dragged off to the server. Put this script after the end tag of the UpdatePanel - it has to hold onto the value of the scrolltop variable not get caught up in the refresh cycle.

JavaScript
var scrollTop;

//hook our function up to library

Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginScript);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndScript);


//Get the scrolltop of the control (panel in this case)
function BeginScript(sender, args) 
{
  var elemnt = document.getElementById('pnlOne');
  scrollTop=elemnt.scrollTop;
}

. . .Round trip takes place:

JavaScript
//plug the value back into the panel's scrolltop
function EndScript(sender, args)
{
  var elemnt = document.getElementById('pnlOne');
  elemnt.scrollTop = scrollTop;
} 
</script>

And, that's all there is to it. So thanks for reading, and I wish you all a good – Say what?

You have more than one Panel that needs to maintain scrolltop? That's easy: write an additional pair of scripts for each Panel.

OhhhhhhhhKay. No need to get huffy. . .since the number of panels may change as your app remains in production, let's put all the panels' IDs and their scrolltops into arrays. Now, all you have to do is add any new ID to the first array.

Note that at one point, we test to make sure that the Panel exists and is visible. This handles controls that have had their visibility turned off.

JavaScript
<script type="text/javascript">
var pnlArray=['pnlQuarters', 'pnlEngines'];
var stArray= new Array();

Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginScript);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndScript);

function BeginScript(sender, args) 
{
    for (i = 0; i<2; i++)
    {
        var elemn = document.getElementById(pnlArray[i]);

    if (null != elemn)
    {
        stArray[i]=elemn.scrollTop;
    }
}

. . .Round trip takes place:

JavaScript
function EndScript(sender, args)
{
    for (i = 0; i<2; i++)
    {
        var elemn = document.getElementById(pnlArray[i]);

        if (null != elemn)
        {
            elemn.scrollTop = stArray[i];
        }
    }
} 
</script>

You might also be interested to know that the EndRequest is the absolute last event you can hook into during the request lifecycle; if you would like to be notified earlier - before the content is rendered, you can register with the Sys.WebForms.PageRequestManager.PageLoading or the Sys.WebForms.PageRequestManager.PageLoaded events.

History

  • Version 1.0 - 3/8/2009.

License

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