Wednesday, December 28, 2005

ASP.NET : Trigger an Event in the Parent Page from within a Page inside IFrame

Web Applications have started dominating the traditional Windows based applications and in the process, the requirements are ever increasing and in many situations we find that too many functionalities need to come as part of a single page.

At this point we resolve to break down each of these functionalities as separate pages and try to call these pages inside an IFrame. This will ensure that the page does not become too heavy and also easy maintainability.

The problem starts when an event has to be triggered in the parent page after an event occurs inside the IFrame. Now let us take an example before I explain how this can be accomplished.

Let us say we have a page that contains Product information. I have some ten list of options like adding categories of a Product, Viewing Product information, etc.

Now for all these I want to give a singly entry system, with my Parent page where I show up all the products in a list box. On top of this I have all the ten options specified. All the other functionalities are taken to ten different pages. I keep an IFrame beside my ListBox that calls the 10 different pages.The aspx page for the Parent Page (say ProductHome.aspx) will have this code:

<table class="table" height="60%" width="80%" align="center" border="0">
<tr>
<td width="20%" height="100%">
<asp:linkbutton id="lnkAddProduct" CssClass="sublink" Runat="server" text="Add a New Product"></asp:linkbutton>
<asp:listbox id="lstProducts" CssClass="textbox" Runat="server" width="100%" Rows="10" AutoPostBack="True" ></asp:listbox></td>
<td width="80%" height="100%"><iframe id="MyFrame" style="WIDTH: 100%; HEIGHT: 100%" name="main" src="" frameBorder="0" scrolling="yes" height="100%" runat="server"></iframe>
</td>
</tr>
</table>


On click of every product I show up the page that displays Product information. Another page is for creating a new Product. Whenever I create a new Product in a page inside an IFrame, I must be able to add that product to the ListBox in the Parent page.

For this to happen, all I need to do is just a few lines of code in the code behind of the child page that contains the new Product information (let us call it AddProduct.aspx). In the Button Click Event (say btnSave_Click), after saving the information to the database:

Session["BindProducts"] = "true";
Session["Page"] = "AddProduct.aspx";
Session["ShowMessage"] = "The Product was added successfully";
Page.RegisterStartupScript("RefreshParent","<script language='javascript'>RefreshParent()</script>");


All that the above code does is to refresh the parent page after setting a few Session variables.

Now in the aspx page of the child page, add the following script block:

<script language="javascript">
function RefreshParent()
{
window.parent.location.href = "ParentPage.aspx";
}
</script>


So now when the Parent Page refreshes, you must bind the ListBox again with the new Product added. For this to happen, add the following code in Page_Load event of Parent Page (ProductHome.aspx):

if(Session["BindProducts"] != null)
{
if(Session["BindProducts"].ToString() == "true")
{
//Call the function to Bind the Products list box again
BindProducts();
}
}

And now remember to set the IFrame's src attribute to your child Page with a message displaying that the Product was added successfully. This will give the end user a feeling that he is only working with the one single page. Add the following code in Page_Load event of ProductHome.aspx to set the URL of the IFrame:


#region Set URL of IFrame
if(Session["Page"] != null)
{
string url = Session["Page"].ToString();
HtmlControl MyFrame = (HtmlControl)this.FindControl("MyFrame");
MyFrame.Attributes["src"] = url;
Session["Page"] = null;
}
#endregion


Now as a last step, display the message that the Product was added in the Child Page. Add the following code in Page_Load event of the Child Page (AddProduct.aspx):

if(Session["ShowMessage"] != null)
{
lblVisitsAdded.Text = Session["ShowMessage"].ToString();
Session["ShowMessage"] = null;
}

The above steps can be repeated for all the other functionalities.

Thursday, December 22, 2005

How To : Create a Dynamic Time Table in ASP.NET

There will be many times where you will need to have a time table in your applications.

For ex. Think of a travel agency person who wants to mark that the buses/transport facilities would be available on specific times - on specific days. This availability information is prone to have frequent changes and therefore must be editable also.

Lets suppose that we maintain two tables - one for number of days the bus will run on like Sunday, Monday, etc. - we will call it AvailabilityDays, and one for the timings - such as 8:30, 9:00, 9:30, 11:30, 12:30 , etc. called AvailabilityTimes.

All we need is an editable grid with checkboxes, with columns as AvailabilityTimes and rows as the AvailabilityDays.

The first step is to get data through a Stored Procedure into a DataSet. The following code gets the code into a DataSet from an SP that has just 2 select statements from the


DataTable avDaysTable = new DataTable();
DataTable avTimeTable = new DataTable();
DataSet ds = new DataSet();
SqlConnection sqlCon = new SqlConnection("server=[servername];uid=[username];pwd=[password];database=[database]");
SqlCommand cmd = new SqlCommand("GetAvailabilityDaysAndTime",sqlCon);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter dAdapter = new SqlDataAdapter(cmd);
dAdapter.Fill(ds);
avDaysTable = ds.Tables[0];
avTimeTable = ds.Tables[1];


Now that we have two datatables with Days and Times, we now need to construct a dynamic table containing these. For that we first declare an empty table in the aspx page:

<asp:table CssClass="sub-heading" id="MyTable" Width="100%" Height="50%" Runat="server" Font-Size="10" Font-Name="Arial" BorderStyle="Ridge" GridLines="Both"></asp:table>

Now to build the dynamic time table, we first take the count of the columns that need to come, and first construct the header row with the number of columns.

int columnCount = avTimeTable.Rows.Count;
TableItemStyle tableStyle = new TableItemStyle();
tableStyle.HorizontalAlign = HorizontalAlign.Center;
tableStyle.VerticalAlign = VerticalAlign.Middle;
tableStyle.Width = Unit.Pixel(100);
tableStyle.CssClass = "button-editable-heading";

TableRow headerrow;
TableCell headerCell;
headerCell = new TableCell();
headerCell.Text = "";
headerrow = new TableRow();
headerrow.Cells.Add(headerCell);
for(int i=0;i<avTimeTable.Rows.Count;i++)
{
headerCell = new TableCell();
headerCell.Text = avTimeTable.Rows[i][1].ToString();
headerrow.Cells.Add(headerCell);
}
headerrow.ApplyStyle(tableStyle);
MyTable.Rows.Add(headerrow);


Next, for each of the columns in the header, we now need to add as many number of days as in the AvailabilityDays table. This will constitute the rows of the table. To each of these rows, we also need to add a Checkbox so that the availability for a day and for a particular time can be set. Finally, add these rows to the main table to complete the time table grid.

CheckBox chk;
for(int i=0;i<avDaysTable.Rows.Count;i++)
{
//add rows to first column
headerrow = new TableRow();
headerrow.ID = i.ToString();
headerCell = new TableCell();
headerCell.Text = avDaysTable.Rows[i][1].ToString();
headerCell.ApplyStyle(tableStyle);
headerrow.Cells.Add(headerCell);
for(int j=0;j<avTimeTable.Rows.Count;j++)
{
headerCell = new TableCell();
chk = new CheckBox();
chk.ID = "chk" + i.ToString() + j.ToString();;
headerCell.Controls.Add(chk);
headerrow.Cells.Add(headerCell);
}
MyTable.Rows.Add(headerrow);
}
MyTable.Rows.Add(headerrow);

Thats it. We now have a time table grid ready to take the values for any day any time. In the next article, I'll explain how we can store and retrieve values in the time table.




How to : Get Excel data into a .NET DataSet object

There may be requirement where in data in an Excel sheet has to be manipulated inside .NET Applications. Although there are many ways to achieve this (like using Excel object of COM), the most preferred way is to use the .NET Data providers to achieve this.

The following is a sample code that can be used to get Excel data into a .NET Dataset object.

The parameter excelSheetName in the below method is the name of the Excel sheet and the sheetNumber parameter indicates the number of the sheet (out of all the sheets in an Excel file).

public DataSet RetrieveExcelData(string excelSheetName,int sheetNumber)
{
OleDbConnection objConn = null;
System.Data.DataTable dt = null;
try
{
// Connection String.
String connString = "Provider=Microsoft.Jet.OLEDB.4.0;" +
"Data Source=" + excelSheetName + ";Extended Properties=Excel 8.0;";
// Create connection object by using the preceding connection string.
objConn = new OleDbConnection(connString);
// Open connection with the database.
objConn.Open();
// Get the data table containg the schema guid.
dt = objConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
if(dt == null)
{
return null;
}
String[] excelSheets = new String[dt.Rows.Count];
int i = 0;
// Add the sheet name to the string array.
foreach(DataRow row in dt.Rows)
{
excelSheets[i] = row["TABLE_NAME"].ToString();
i++;
if(i==sheetNumber)
break;
}
OleDbCommand excelCommand = new OleDbCommand("Select * from ["+excelSheets[sheetNumber-1]+"]",objConn);
OleDbDataAdapter excelAdapter = new OleDbDataAdapter(excelCommand);
DataSet excelDataSet = new DataSet();
excelAdapter.Fill(excelDataSet);
return excelDataSet;
}
catch(OleDbException ex)
{
throw ex;
}
catch(Exception ex)
{
throw ex;
}
finally
{
// Clean up.
if(objConn != null)
{
objConn.Close();
objConn.Dispose();
}
if(dt != null)
{
dt.Dispose();
}
}
}


Through the above method, we avoid the extra reference of the COM dll and also get the flexibility of manipulating Excel shett data through ADO.NET.