Search This Blog

Friday, August 26, 2011

Working with frames in asp.net


Introduction

Any developer who has had the dubious task of developing a Web site utilizing frames knows it can be an uphill battle. Many argue that frames should be avoided at all costs, while others realize how they can benefit a Web site's user interface. When my team had to develop a Web site that displayed pdf's in a management dashboard, we knew frames were the way to go. Unfortunately, we were not sure how to best work with frames in ASP.NET. When I used my list of normal search engines/newsgroups, I could not find much in the way of useful information. Here is one reply I got when asking how to best work with frames in .NET:

Ok, My list of suggestion for Frames.
  1. Frames are Evil
  2. The Devil created Frames
  3. If you are having a problem related to the Target, refer to item 1
  4. If you are trying to refresh data in a particular frame, refer to 1
I think you get the idea. The fact of the matter is frames can provide an aesthetically pleasing site, which in many situations is more user-friendly. This of course is only the case if you avoid the pitfalls of frames, such as multiple scroll bars and too many frame windows. I will not address these pitfalls in this article because there is certainly an over-abundance of information/opinions on that subject. In my opinion frames have gotten a bad reputation from users because so many developers misused them. On the other hand they have a well deserved reputation from the developer side because in the past they were very difficult to work with. The purpose of this article is to show you that this is no longer the case. I will also present one alternative to using frames, called Smart Navigation.

In the end we solved the problem of working with frames by utilizing a mix of JavaScript code and the Attributes property of .NET Web Forms. This is essentially the key behind working with frames in .NET.
Frames and Frame Alternatives

Although this article will be used to address the usage of frames in .NET, it is important to talk about the use of frames and some alternatives. My team has developed the majority of our sites without frames because they did not fit into a model in which they were required. We use frames when we need to present the users with a control or set of controls that maintain a certain state, while another part of the page needs to load a file in or some other type of control(s). The MSDN site illustrates a good use of frames in its library section: http://msdn.microsoft.com/library/default.asp In the past we have also used frames to control screen refresh. On an ASP 2.0 or 3.0 page, the entire page would refresh any time you needed to perform a server side event. One alternative to solving the screen refresh issue in ASP.NET is Smart Navigation. This tag can be set at page level through page properties, or at site level through web.config. With this turned on, only the controls within the form tag will be refreshed. So if you have other images, headers, etc., the users will not get screen flicker. It is important to note that behind the scenes ASP.NET is using Iframes, so this will only work on IE5.0 and greater browsers. The use of inline frames allows for this targeted refresh because each frame is treated independently. The following link will provide you with more information on Iframes and some issues related to them: http://www.cs.tut.fi/~jkorpela/html/iframe.html 

To see how Smart Navigation can be used, let's take a look at an example.

<%@ Page Language="vb" AutoEventWireup="false" 
Codebehind="smart_tag.aspx.vb" Inherits="asptoday_frames.smart_tag" 
smartNavigation="True"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
	<HEAD>
		<title>smart_tag</title>
		<meta content="Microsoft Visual Studio.NET 7.0" 
name="GENERATOR">
		<meta content="Visual Basic 7.0" name="CODE_LANGUAGE">
		<meta content="JavaScript" name="vs_defaultClientScript">
		<meta content="http://schemas.microsoft.com/intellisense/ie5"    
name="vs_targetSchema">
	</HEAD>
	<body MS_POSITIONING="GridLayout">
		<form id="frmImage" style="Z-INDEX: 101; LEFT: 6px; POSITION: 
absolute; TOP: 18px" runat="server">
<asp:button id="btnRefresh" style="Z-INDEX: 104; LEFT: 19px; POSITION: 
absolute; TOP: 13px" runat="server" Text="Refresh"></asp:button><asp:label id="lblRefresh" 
style="Z-INDEX: 105; LEFT: 96px; POSITION: absolute; TOP: 18px" 
runat="server"></asp:label></form>
		<IMG style="Z-INDEX: 103; LEFT: 27px; WIDTH: 507px; POSITION: 
absolute; TOP: 87px; HEIGHT: 259px" height="259" alt="" src="test.GIF" 
width="507">
		<IMG style="Z-INDEX: 102; LEFT: 16px; WIDTH: 507px; POSITION: 
absolute; TOP: 78px; HEIGHT: 259px" height="259" alt="" src="test.GIF" 
width="507">
	</body>
</HTML>
Code Behind

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class smart_tag : System.Web.UI.Page
{
	private System.Web.UI.WebControls.Button withEventsField_btnRefresh;
	protected System.Web.UI.WebControls.Button btnRefresh {
		get { return withEventsField_btnRefresh; }
		set {
			if (withEventsField_btnRefresh != null) {
				withEventsField_btnRefresh.Click -= btnRefresh_Click;
			}
			withEventsField_btnRefresh = value;
			if (withEventsField_btnRefresh != null) {
				withEventsField_btnRefresh.Click += btnRefresh_Click;
			}
		}
	}

	protected System.Web.UI.WebControls.Label lblRefresh;
	private void Page_Load(System.Object sender, System.EventArgs e)
	{
		this.lblRefresh.Text = "";
	}

	private void btnRefresh_Click(System.Object sender, System.EventArgs e)
	{
		this.lblRefresh.Text = "Page Refreshed";
	}
	public smart_tag()
	{
		Load += Page_Load;
	}
}

At the top of the page we are setting smartNavigation="True" so that the only the lblRefresh and btnRefresh make a roundtrip to the server. I have added two images at the bottom of the page to illustrate the difference that smart navigation makes. If you set smartNavigation = False, you will notice some flashing of the images, even when running local to the Web server. IFrames can be useful in forcing refresh of only part of the screen, but it cannot fully replace the functionality that comes with working with regular frames. The biggest drawback with Iframes is that you cannot implement two separate form tags in the same page, which may be required for some sites.

Questions Regarding Frames
The main problem that most people have when working with frames is cross-frame communication. There are several consistent questions I see on frames including:
  • How do I pass data to another frame?
  • How do I refresh a specific frame?
    This issue is compounded when you start dealing with pop-up windows.

The Solution
Solving the above described problems has become much easier with some basic knowledge of the windows and frames properties in javascript and the ASP.NET Attributes property. In this article I am going to break my code up into several sections, starting with a very easy to follow hello world style example. After that I will address working with pop-ups, adding a very useful HTML control into the mix.
The Code

Goal #1: Pass data between left and right frames
UI Design
Before I get into the code, it is important that you receive the visual of the two frames that we will be working with:
CODE
For the purpose of brevity, I will exclude code automatically generated by Web forms.
To start we have the simple default.htm page that will contain our two aspx pages.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
	<head>
		<title>How to Work with frames in .Net</title>
		<meta name="vs_defaultClientScript" content="JavaScript">
		<meta name="vs_targetSchema" 
content="http://schemas.microsoft.com/intellisense/ie5">
		<meta name="GENERATOR" content="Microsoft Visual Studio.NET 
7.0">
		<meta name="ProgId" content="VisualStudio.HTML">
		<meta name="Originator" content="Microsoft Visual Studio.NET 
7.0">
	</head>
	<frameset border="1" frameborder="1" framespacing="0" 
cols="30%,70%">

<frame name="left frame" src="leftframe.aspx"/>
<frame name="right frame" src="rightframe.aspx"/>

</frameset>
</html>
Next is the left frame page, which will contain a mix of javascript and VB.NET code. The mix of this code will enable us to pass a text string from a textbox on the left frame to a textbox in the right frame.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class leftframe : System.Web.UI.Page
{
	protected System.Web.UI.WebControls.Button btnTright;
	protected System.Web.UI.WebControls.Button btnPop;

	protected System.Web.UI.WebControls.TextBox txtTright;
	private void Page_Load(System.Object sender, System.EventArgs e)
	{
		//enclose the add attributes in the not is post back block, so they
		// are called to be added only once.
		if (!IsPostBack) {
			btnTright.Attributes.Add("onclick", "javascript:tranRight(txtTransferRight.value)");
			btnPop.Attributes.Add("onclick", "javascript:openWindow()");
			//remove file name from session
			Session.Remove("fileName");
		}

	}
	public leftframe()
	{
		Load += Page_Load;
	}

}
The key to working with frames is the Attributes property along with its Add method, which allows us to dynamically insert calls to javascript functions, passing in our server side control's data. The Attributes property allows you to declare any event handler that is associated with a specific Web control. Any of the attributes that you add to the controls collection will be rendered at run time. For a text box you could add a call to the textchanged event. Note, if you do make reference to an unsupported event, it will be ignored by the browser. For now you can ignore the session remove, which will be needed for a later part of this article. Here is the HTML code that exists for the left frame page.

<%@ Page Language="vb" AutoEventWireup="false" 
Codebehind="leftframe.aspx.vb" Inherits="asptoday_frames.leftframe"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
	<HEAD>
		<title>leftframe</title>
		<meta content="Microsoft Visual Studio.NET 7.0" 
name="GENERATOR">
		<meta content="Visual Basic 7.0" name="CODE_LANGUAGE">
		<meta content="JavaScript" name="vs_defaultClientScript">
		<meta content="http://schemas.microsoft.com/intellisense/ie5" 
name="vs_targetSchema">
	</HEAD>
	<body MS_POSITIONING="GridLayout">
<form id="frmTransterRight" method="post" runat="server">
<asp:button id="btnTransferRight" style="Z-INDEX: 101; LEFT: 23px; 
POSITION: absolute; TOP: 100px" runat="server" Text="Send to right frame" 
Width="131px"></asp:button><asp:textbox 
id="txtTransferRight" style="Z-INDEX: 102; LEFT: 26px; POSITION: absolute; TOP: 
64px" runat="server" Width="170px" Height="24px">Text to go to right 
frame</asp:textbox><asp:button id="btnPop" style="Z-INDEX: 103; 
LEFT: 24px; POSITION: absolute; TOP: 136px" runat="server" Text="Open Pop-Up" 
Width="129px"></asp:button></form>
<script language="javascript">		

//this function takes a value (ltext) and transmits that to the left hand frame

function tranRight(ltext)
{
   parent.frames(1).document.forms("frmReceive").item("txtReceive").value = ltext;	
}
</script>
</body>
Notice the tranRight javascript function, which will enable us to transfer the text in the left frame to the right frame. The tranRight function is called from the click event of the btnTransferRight button Parent.Frames(1) indicates we are targeting the right frame, while Parent.Frames(0) is the current page that the code is getting initialized from. When the btnTransferRight button is clicked, only the left frame will be re-rendered because the button's event is set to run client side. 


With that simple code, we have established basic interaction between our frames.

Goal #2: Pass text between pop-up and right frame
In this section we will pass text from a pop-up screen launched from the left frame to a textbox in the right frame. UI Design The "Send Via QS" button will trigger the event that will transmit the text. The lower section of this UI will be addressed in Goal#3

UI Code
The first key is the code which allows this pop-up to be launched from the left frame. This is a line in the original left frame codebehind listed as part of example #1:
 
btnPop.Attributes.Add("onclick", "javascript:openWindow()")
This is the JavaScript which launches this pop-up:

function openWindow() 
{ msgWindow=window.open("popup.aspx","", 
"fullscreen=no,toolbar=no,status=yes,menubar=yes,scrollbars=no,resizable=no,
directories=no,location=no,width=500,height=400"); 
if (msgWindow.opener == null) msgWindow.opener = self; }
Now that we have established the launch of the popup, we can look at the pageload event of the popup Web form.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class popup : System.Web.UI.Page
{
	protected System.Web.UI.HtmlControls.HtmlInputButton btnLfile;
	protected System.Web.UI.WebControls.Button btnWFrame;
	protected System.Web.UI.WebControls.Button btnHframe;
	protected System.Web.UI.WebControls.TextBox txtPop;

	protected System.Web.UI.HtmlControls.HtmlInputFile myFile;
	private void Page_Load(System.Object sender, System.EventArgs e)
	{
		//enclose the add attributes in the not is post back block, so they
		// are called to be added only once.
		if (!IsPostBack) {
			btnSendQS.Attributes.Add("onclick", "javascript:openpdf(txtPop.value)");
			btnSendSession.Attributes.Add("onclick", "javascript:openviacache()");
		}
	}
	public popup()
	{
		Load += Page_Load;
	}
}
Once again we are adding attributes to two of the buttons which call javascript functions. The first button is the one we will concentrate on for the time being. As you can see we are passing in the value of the txtPop textbox. Here is the javascript which transmits the text.

function transferText(strTxtTransfer) { 
window.opener.parent.frames[1].location.href = "rightframe.aspx?strText="+ strTxtTransfer; window.close(); }
By using window.opener.parent we are able to reference the original frame, so we then in turn can reference the right frame to transmit the data to (by inserting and index of 1). The text itself is encased within a query string. This query string is then read by the right frame on load of the page and presented within that page's textbox.

Goal #3 Use HTML input control in pop-up to load user file to server and then launch pdf in right fr
In this section we will be using the HTML input control to load a pdf file to the Web server and then subsequently call for the load of that pdf into the right frame. The HTML input control, which is provided with .NET, makes file uploading far more simple by presenting users with the standard Windows file browsing window. UI Design

UI Code

<form id="Form1" method="post" encType="multipart/form-data" 
runat="server">
The first section of code to review is the HTML in the pop-up.aspx page. In the form you must add the Tag encType="multipart/form-data". This will allow the HTML input control to work.

 private void btnLoadFile_ServerClick(System.Object sender, System.EventArgs e)
{
	//Grab the file name from its fully qualified path at client 
	string strFileName = myFile.PostedFile.FileName;
	// only the attched file name not its path
	string strShortFile = System.IO.Path.GetFileName(strFileName);
	//Save uploaded file to server @ rootweb\pdf_files and add to session
	myFile.PostedFile.SaveAs(Server.MapPath(".") + "\\pdf_files\\" + strShortFile);
	Session.Add("fileName", Server.MapPath(".") + "\\pdf_files\\" + strShortFile);
}
This codebehind for the loadfile button saves the file up to the server and stores the name and location in session. This was the reason for the clearing of session that was seen in the codebehind of the left frame page. The browse button is what presents the user with the standard Windows Explorer file selection box and fills in the lower text box. This functionality is all encapsulated in the HTML input control. Below is the HTML and JavaScript that exists behind the pop-up aspx page.

<%@ Page Language="vb" AutoEventWireup="false" Codebehind="popup.aspx.vb" 
Inherits="asptoday_frames.popup"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
	<HEAD>
		<title>popup</title>
		<meta content="Microsoft Visual Studio.NET 7.0" 
name="GENERATOR">
		<meta content="Visual Basic 7.0" name="CODE_LANGUAGE">
		<meta content="JavaScript" name="vs_defaultClientScript">
		<meta content="http://schemas.microsoft.com/intellisense/ie5" 
name="vs_targetSchema">
	</HEAD>
	<body MS_POSITIONING="GridLayout">
		<form id="frmPopUp" method="post" encType="multipart/form-data" 
runat="server">
			<asp:label id="Label1" style="Z-INDEX: 106; LEFT: 26px; 
POSITION: absolute; TOP: 90px" runat="server" Height="20px" ForeColor="Red" 
Font-Bold="True" Width="280px">Session 
Example</asp:label><asp:label id="lblSession" style="Z-INDEX: 
107; LEFT: 26px; POSITION: absolute; TOP: 117px" runat="server" Height="20px" 
ForeColor="DimGray" Font-Bold="True" Width="459px" Font-
Size="Smaller">Click Browse, Find PDF file, click load file and then the 
send via session button</asp:label> 
			<INPUT id="btnLoadFile" style="Z-INDEX: 100; LEFT: 334px; 
WIDTH: 79px; POSITION: absolute; TOP: 148px; HEIGHT: 23px" type="button" 
value="LoadFile " runat="server">
			<asp:button id="btnSendSession" style="Z-INDEX: 101; 
LEFT: 18px; POSITION: absolute; TOP: 179px" runat="server" Width="112px" 
Text="Send Via Session"></asp:button><asp:button 
id="btnSendQS" style="Z-INDEX: 102; LEFT: 266px; POSITION: absolute; TOP: 41px" 
runat="server" Width="113px" Text="Send Via 
QS"></asp:button><INPUT id="myFile" style="Z-INDEX: 103; 
LEFT: 18px; WIDTH: 311px; POSITION: absolute; TOP: 148px; HEIGHT: 22px" 
type="file" size="32" name="myFile" runat="server">
			<asp:textbox id="txtPop" style="Z-INDEX: 104; LEFT: 25px; 
POSITION: absolute; TOP: 43px" runat="server" Width="225px">Transmit to 
right frame</asp:textbox><asp:label id="lblQS" style="Z-INDEX: 
105; LEFT: 32px; POSITION: absolute; TOP: 13px" runat="server" ForeColor="Red" 
Font-Bold="True" Width="251px">Query String 
Example</asp:label></form>
		<script language="javascript">			

function transferText(strTxtTransfer)
{
window.opener.parent.frames[1].location.href = "rightframe.aspx?strText="+ strTxtTransfer;
window.close();
}


function openviacache()
{
window.opener.parent.frames[1].location.href = "rightframe.aspx";
window.close();
}
		</script>
	</body>
</HTML>
A refresh of the right frame is called from the "Send Via Cache" button, which has an onclick event call to the openviacache JavaScript function. This function calls for the re-load of the right frame page. We want the page to reload so the code in the page load event of the right hand frame page will execute. The code behind of the right frame page looks for the fname session key and loads a pdf in the page based on that filename. In this block of code, I first test to make sure the file passed from session is a pdf. This is because I am using Response.contentType so that the browser can interpret the file as a pdf and display it properly in the browser.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class rightframe : System.Web.UI.Page
{

	protected System.Web.UI.WebControls.TextBox txtFleft;
	private void Page_Load(System.Object sender, System.EventArgs e)
	{
		//Put user code to initialize the page here

		string strTxtTransfer = Request.QueryString.Get("strText");
		//first find out if this page is getting called from the send via qs button by looking for a query string
		if (!string.IsNullOrEmpty(strTxtTransfer)) {
			this.txtReceive.Text = strTxtTransfer;
		// if session fname is populated and the file is a pdf load the pdf
		} else if (!string.IsNullOrEmpty((Session["filename"])) & Strings.Right((Session["filename"]), 3) == "pdf") {
			Response.Expires = 0;
			Response.Buffer = true;
			Response.Clear();
			Response.ContentType = "application/pdf";
			Response.WriteFile(Session["filename"]);
			Response.End();
		//if session has a value and it is not a pdf send an error message
		} else if (!string.IsNullOrEmpty((Session["filename"])) & Strings.Right((Session["filename"]), 3) != "pdf") {
			var _with1 = this.txtReceive;
			_with1.ForeColor = Color.Red;
			_with1.Text = "sorry you must choose a pdf file to display";
		}
	}
	public rightframe()
	{
		Load += Page_Load;
	}
}
The final result looks like this:

 

No comments:

Popular Posts