Thursday, October 25, 2007

Hover Menu inside a gridview

It's been a while since I found myself doing something that's trickey and not available on google the way I wanted.

Problem:
Scenario1
So, what are we talking about here? Lets say We have a gridview and have some command fields inside the grid, like Edit, Delete, NavigatetoURL and so on. It'll look clumsy to have all these commands (linkbuttons or hyperlinks) in seperate columns or even in one column.


Scenario2
Sometimes, we might want some commands to show up based on the row's data. In the page that I was working on, I have to show PRINT, EMAIL, EDIT and PREVIEW buttons for each row in a gridview. Moreover, not all rows have the same buttons. Some rows cannot be editied and some rows cannot be emailed (when there is no email address to email).


Solution:
So, what can we do to have the above two functionalities and at the same time make our gridview look pretty? Well here we are:



The above is a regular gridview that we all might have seen and implemented. It's plain and doesn't implement the command fields as of now. As you know we usually have the last or first column containing command fields like EDIT (UPDATE & CANCEL), DELETE and so on.


Now, don't get discouraged yet because, our final product will look something like this:

(Ignore the blue vertical column. It is a portion of the page background and will do us no harm). Now notice that the row style changes when you do a mouseover event over it. Also, you get a div popup showing some menu buttons (they're actually databound command fields!). That popup is what this article is about.



Pretty cool eh? This works for row specific command fields (like we discussed earlier) as well. We just have to tweak it.


Behind the scenes:
So, what's happening? Let's start from the markup of the gridview. Since I am assuming that you already have a working knowledge on the gridview and template fields, I am not going to go into details.

--------------------------------------------------------


The template field markup: (click for larger image)
Now this is the template for the last column. That is where we're going to add our div popup. As you can see, I currently have two link buttons inside the div. You can have any number of buttons you want. Also, you can apply styles to it to make it look pretty. I leave that portion to you.

As you can see, I am placing the div tag with all the applicable menu items (as linkbuttons) within the last column's itemtemplate section. Note that I also have set the z-index of the div tag and an absolute positioning. This is vital for the positioning of the div. Since I have the div insite the itemtemplate, I can bind it with a dataitem and even give it a commandname. Sweet!

---------------------------------------

The Client side script to toggle the div's visibility ON and OFF:

// Shows DIV popup commands for gridview
function ShowPopup(lbtn1,lbtn2, panel, grid)
{
var link1 = document.getElementById(lbtn1);
var link2 = document.getElementById(lbtn2);
var pnl = document.getElementById(panel);
var grd = document.getElementById(grid);
pnl.style.display = "block";
//grd.style.backgroundColor="#ffff00";
grd.style.backgroundImage = "url(../images/td_mouseover.gif)";
if(link1 != null)
link1.style.display = "block";
if(link2 != null)
link2.style.display = "block";
//pnl.style.backgroundColor='#99cccc';
pnl.style.backgroundImage = "url(../images/td_mouseover_inverted.gif)";
}
//Hides DIV popup commands for gridview
function HidePopup(lbtn1,lbtn2, panel, grid)
{
var link1 = document.getElementById(lbtn1);
var link2 = document.getElementById(lbtn2);
var pnl = document.getElementById(panel);
var grd = document.getElementById(grid);
grd.style.backgroundImage="url(../images/spacer.gif)";
pnl.style.display = "none";
if(link1 != null)
link1.style.display = "none";
if(link2 != null)
link2.style.display = "none";
}

Understanding this script is very simple. The parameters you saw in the two functions ShowPopup and HidePopup are described below:

  • lbtn1: LinkButton1. I just named it this way so that I'l know that it is the first button in our menu. Remember to databind it because you're putting it inside a gridview.
  • lbtn2: LinkButton2. The second link button in the div popup. As I mentioned earlier, you can have any number of controls (usually buttons or hyperlinks for our purposes) inside the div. All you then have to do is add the id of that control as a parameter in your javascript functions and set the visibility to 'none' or 'block'.
  • panel: I just call the div layer as panel becuase, essentially they're the same. You have to toggle visibility for the div (or the panel as I call it here) on and off first before you toggle the visibility of the menu items inside it (the linkbuttons and/or any other controls that you might have).

Customizing the menu items inside the div popup:
If you see the image of the new gridview, it will show you options to PRINT and EMAIL. You can set the visibility of these items ON or OFF on the row's mouseover event. That you already know. Now, the interesting part is, you can display row-specfic command fields based on a value from the row. That means, you can display only the PRINT button for certain rows, and EMAIL button for certain rows. Of course you can have permutations and combinations for this. How do we go about doig that?

Well, here is the Row Data Bound event for the gridview. As we know, this event fires up when a gridview row is data-bound. So, this is probably the best place to set row specific scripts and conditions.


protected void gvwMyReferrals_RowDataBound(object sender, GridViewRowEventArgs e)
{
// Implement below logic if needed
// Get Status and show toggle linkbutton visibility
if (e.Row.RowType == DataControlRowType.DataRow)
{
LinkButton lbtnGrdPrint = (LinkButton)e.Row.Cells[6].FindControl("lbtnGrdPrint");
LinkButton lbtnGrdEmail = (LinkButton)e.Row.Cells[6].FindControl("lbtnGrdEmail");
HtmlGenericControl panel = (HtmlGenericControl)e.Row.Cells[6].FindControl("gvwMyReferralsDiv");
string showPopup = "ShowPopup('" + lbtnGrdPrint.ClientID + "','" + lbtnGrdEmail.ClientID + "','" + panel.ClientID + "','"+e.Row.ClientID+"')";
string hidePopup = "HidePopup('" + lbtnGrdPrint.ClientID + "','" + lbtnGrdEmail.ClientID + "','" + panel.ClientID + "','" + e.Row.ClientID + "')";
e.Row.Attributes.Add("onmouseover", "javascript:"+showPopup);
e.Row.Attributes.Add("onmouseout", "javascript:" +hidePopup);
}
}


I first check to see if the row is a data row (which eliminates pager, header and footer rows). Then, I get the instance of the controls that make up the div popup menus. First I get the instances of the linkbuttons and then I get the instance of the div tag. Notice that I am casting the div as a panel? Well, that is not casting! Since the panel is a div tag, this method works fine and does not throw casting exceptions. If you have more controls inside the div, get instances of those controls and also the instance of the div.

Now that we have instances of the neccasary contols, it is time to call the client script functions but, not just yet. I'll explain what I have not implemented as of yet. Remember me writing that you can have row-specific menu items inside the div? Let's discuss about that a little. In the row data bound, we can get some value from the data source that is bound to a boundfield or template field, and based on that, we can manipulate the visibility of the controls inside the link button. In my example (see the image of the new gridview), I have set the div tag to be a part of the last column with name as STATUS. Now, I can use STATUS's cell data to display the linkbuttons. All I have to do is, within an if statement, check the STATUS's string value and based on any condition, I can set the value of a variable (that I have declared before) to either the client ID of the control(if I want to show it inside the div) or just pass the variable to the script (instead of the client ID). In short, make sure to pass to the javascript the actual client ID of the control you want to show up and a ZERO VALUE instead if you don't want it to show up. So, when I pass the empty string to the script, it ignores that parameter based on this condition:

if(link1 != null) link1.style.display = "none";

This will make sure the script doesn't blow up. Now, when you hover over each row, you can have menu specific items inside your div popup. Since I am also passing e.Row to the script, I can make the whole row's style change on an onmouseover event. That will give the grid a look and feel of a clickable row. Of course you can extend it by adding styles through javascript. Pretty cool eh?

Style's files:
Well what did I miss? Ah, the images! I'll just put the two images that I used to set the background of the row when you mouse your mouse pointer over it.


The above is used to set the style for the gridview row when you do an onmouseover event.

The below image is used by the div as a background. You can change anything you want to fit your needs.

Questions? Sure! Suggestions? Even better! I like to know how this can be enhanced and/or tuned. I can be reached at vimaljonn@gmail.com.

Wednesday, May 2, 2007

Single Sign-On using ASP.NET

For many, this seems to be a highly complex issue. Well, if you stop thinking hi-tech, you can make it simple and secure (to a certain extent).

Introduction and Assumptions:
Since it is fairly easy to implement the same in the intranet, we'll talk more about the extranet implementation. Let call our main application (the portal) as the Master. All our users have access to the Master (with role based security). However, not all have access to the child applications within Master.

Scenario:
Let us assume a user-- Bob. Bob is an accountant for two different departments( A and B) at his work. He will need to log into the departments' websites to pull sensitive reports. Now, our Master application will let Bob in with his own credential settings. Within that application, he'll see the links to websites for departments A & B. When he clicks on either of those, he's directed to the corresponding department's homepage (not the login page) bypassing the login process.

Behind the Scenes:
We need to setup few tables to do this single sign-on which enabled Bob to bypass the login process from the Master application. I'll explain the table layout and thier functionality below.

tbl_App: This table has the list of child applications to be brought into single sign-on
  • AppID
  • AppName
  • AppURL

tbl_AppUsers: This table has the list of users and the Application ID (from the previous table) that they have access to

  • AppID
  • UserID

tbl_AppUserKey: This table stores the AppID, UserID, and a GUID (which acts more like a authentication token for our single sign-on)

  • AppID
  • UserID
  • Key

These are the key tables that you'll need. Assuming this is not for beginners, I will bypass other topics like user roles for different applications, securing the keys generated (to be discussed later) and so forth.


The Process:

Master:
Choose a data driven control like gridview or treeview to display the Applications that a user has access to. Use the first two tables for this. After Bob logs in to Master, read his user ID and get the applications (departments A and B) that he has access to.

Now this is important! When Bob clicks on the application link (Department A, in this example), you'll need to generate a unique key (GUID, to keep it simple), and store this GUID along with Bob's User ID and the App ID. Read the App's URL from the tbl_App table. Open a new window (You can do this using client script which I'll post at the end) for the link that Bob has just clicked, and pass the generated key as a query string.

Department A's application:
Get the query string (the GUID) and read the corresponding User ID from the tbl_AppUserKey table. Now that you have the User ID for Bob, it's not a good idea to keep the GUID in the table. So, delete it! duh!

In this way, even if Bob closes the browser (instead of logging out), and tries to come back (by entering the URL from his browser history) you can redirect him to a error page as the query string would be null. As I said earlier, I assume you already know about maintaining roles and synchronizing the Master and Child applications.

ClientScript for Opening a new Browser Window:

//Open a window to the app and pass Token as query string - works only on postbacks
string script = "window.open('" + url + "?token=" + key + "')";
ClientScriptManager cm = Page.ClientScript;
cm.RegisterStartupScript(this.GetType(), "window", script, true);

Any questions? shoot me an email at vimaljonn@yahoo.com

WPF/E - ASP.NET (Server Control)

So much hype about the WPF from the developer-evangelists of Microsoft convinced me upto an extent to install the 3.0 framework and the neccasary installers for creating cool WPF (or should I say Microsoft's Flash) content for windows and for the web.Installers are easily available for those who are winforms enthusiasists. However, if you want to develop wpf/e based content for the web, then you better make a checklist of the installers from msdn. Plus, you'll need service pack 1 for Visual Studio 2005. Remember that the SP1 is about 400 Mb. You'll probably want to download it first before installing. Then you need the RC (the latest is Feb 07) for WPF and WPF/E SDK.

Without the SDK you'll not be able to install the WPF template.Apart from this minor information, I found an interesting link which allows you to use a custom user control (credits to Mike Harsh). Click here to Visit the link. User Controls are fun as we know. Here, all we have to do is create a XAML file using XAML Pad, Expression web, or just xml Editor. I'd rather stick with Expression Web. After you create the xaml, use the user control and link the source to the XAML file. Viola! The user Control can be found here and a sample application using the User Control Can be downloaded from here.