Friday, March 6, 2009

Presence In SPGridView

Hot freaking socks... Where are the UFC picks you have been asking. They have been delayed as I worked on what should have been a simple project.
The company I work for will be incorporating Windows Communicator with their VOIP and IM system.
We also have a web based Corporate Directory application.
Wouldn't it be nice, someone thought, if we could put the Windows Communicator Presence Indicator in to our Corporate Directory?
The Corporate Directory is currently a SharePoint Data View Web part. Adding Presence would require custom development.
Off I go.

The first issue I ran in to was that there is some debate on how to get Presence information in to SharePoint 2007, some blogs say to use the IMNRC java script, while others say to use the imnmark image with a IMNImageOnClick java script event.
Confused? Me too. The problem is that Microsoft left the old way to do Presence in to the new version of SharePoint, so what would work in 2003:


<span><img border=\"0\" height=\"12\" src=\"/_layouts/images/imnhdr.gif\" onload=\"IMNRC('"you@domain.com"')\" ShowOfflinePawn=\"1\" alt=\"\" id=\"user_presence_icon\"/></span>


will work in 2007, but the way Microsoft does Presence in 2007 is with the anchor and image tags:


<a href="javascript:" onclick="IMNImageOnClick();return false;" class="ms-imnlink"><img title="" alt="No presence information" name="imnmark" border="0" valign="middle" height="12" width="12" src="/_layouts/images/blank.gif" sip="you@domain.com" id="imn1,type=sip" ></a></span>


Great! It works for just one user, with their email address hard coded. The problem is that I have lots of users, and everything is stored in a database.
Easy!, you say, Just use a GridView and put that stuff in there!! Sounds good, but SharePoint always has a way of messing with us. The SharePoint version of a GridView is the SPGridView. It is just like a GridView, only harder. In SharePoint you do nearly everything in the code. It sucks. Personally, I like the ability to use the Design tap of Visual Studio it lets me know what I have going. So, I tend to use User Controls so that I can see what is going on. Only that does not really apply here.
Anyway, typically what you would do in ASP.NET programming would be to drop a GridView on a page and set up your fields. Because we are using some HTML you will need to create some template fields. Generally looks like this:


<asp:GridView ID="GridView1" runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>


No problem! Except that we are in SharePoint and we have to do all of that in code...

This is where it really gets sticky. You will notice that the Template Field is called as a control. That means that it is a part separate from the GridView. That makes your life in SharePoint difficult.

As Oscar Medina details in his awesome blog post SPGridView - Using a custom TemplateField to add a Checkbox Column you can declare a Template Field inside your SPGridView, but you will need to create a class for your Item Templates.

A class that inherits from ITemplate, and you will need to override the InstantiateIn method in order to stick your stuff in.

During my very frustrating trial and error period, I found that the best way to get the Presence stuff to work is to use the new 2007 method, but convert all of that HTML in to ASP.NET controls. The ITemplate object likes those better. So what I ended up doing was creating two Literal Controls for the span tags, a Hyperlink Control for the anchor, and an Image control for the image. I used the Attributes.Add for all of the extra stuff in the anchor and the image tags that is not supported in the normal control.
So your code from above now becomes:


LiteralControl lc = new LiteralControl();
lc.ID = "lcSpan1";
lc.Text = "";


HyperLink hypLink = new HyperLink();
hypLink.Attributes.Add("onclick", "IMNImageOnClick();return false;");
hypLink.CssClass = "ms-imnlink";
hypLink.NavigateUrl = "javascript:";


System.Web.UI.WebControls.Image imgPresence = new Image();
imgPresence.Attributes.Add("name", "imnmark");
imgPresence.Attributes.Add("id", "imn1,type=sip");
imgPresence.ImageUrl = "/_layouts/images/blank.gif";
imgPresence.Attributes.Add("sip", "you@domain.com");
imgPresence.ID = "imgPresence";


LiteralControl lc1 = new LiteralControl();
lc1.ID = "lcSpan2";
lc1.Text = "
";


Sweet huh? Again, it works great for hard coding a single user to a Presence indicator. We don't want that. We want something we can stick in to a SPGridView to display a whole bunch of people. Here we go.

First you need to set up your data source. I like to use DataTables.


DataTable DT = new DataTable("Data");
DT.Columns.Add("Name");
DT.Columns.Add("Position");
DT.Columns.Add("Presence");
DataRow row = DT.NewRow();
row["Name"] = "Natto Ninja";
row["Position"] = "Goofball";
row["Presence"] = "nattoNinja@domain.com";
DT.Rows.Add(row);

row = DT.NewRow();
row["Name"] = "Megan Fox";
row["Position"] = "Megga Hottie";
row["Presence"] = "iamhot@domain.com";
DT.Rows.Add(row);

row = DT.NewRow();
row["Name"] = "Al Sharpton";
row["Position"] = "King of the Goofballs";
row["Presence"] = "KOG@domain.com";
DT.Rows.Add(row);


DataTables are fun. Now we need to create our SPGridView. There are a couple of different ways to do this, if you are using a control, you can use Ted Pattison's approach or you can just create it in the code. Calling it in the code makes it tougher for you to place on the page, but Pattison's approach requires you to create a code behind (not a code beside, very important distinction. If you don't know the difference you need to read Pattison's article). At any rate you have to create the grid.


SPGridView grdSPGrid = new SPGridView();
//Createyourfields
SPBoundFieldfldName=newSPBoundField();
fldName.HeaderText="Name";
fldName.DataField="Name";
grdSPGrid.Columns.Add(fldName);

SPBoundFieldfldPosition=newSPBoundField();
fldPosition.HeaderText="Position";
fldPosition.DataField="Position";
grdSPGrid.Columns.Add(fldPosition);

TemplateFieldfldPresence=newTemplateField();
fldPresence.HeaderText="Presence";

//Call your class that creates your Item Template
fldPresence.ItemTemplate=newGridViewTemplate("Presence");

grdSPGrid.Columns.Add(fldPresence);

//Call the method that inserts your row information in to your image control
grdSPGrid.RowDataBound+=newGridViewRowEventHandler(grdSPGrid_RowDataBound);

//Bind the the DataTable to the SPGridView
grdSPGrid.DataSource=DT.DefaultView;
grdSPGrid.DataBind();


You will notice the place where I call a class to get my Item Template going. It is in that class where I create my objects that will become my Presence indicator.
Here is that class:


public class GridViewTemplate : ITemplate
{

private string columnName;

public GridViewTemplate(string colname)
{

columnName = colname;
}

public void InstantiateIn(System.Web.UI.Control container)
{

LiteralControl lc = new LiteralControl();
lc.ID = "lcSpan1";
lc.Text = "";
container.Controls.Add(lc);

HyperLink hypLink = new HyperLink();
hypLink.Attributes.Add("onclick", "IMNImageOnClick();return false;");
hypLink.CssClass = "ms-imnlink";
hypLink.NavigateUrl = "javascript:";
container.Controls.Add(hypLink);

System.Web.UI.WebControls.Image imgPresence = new Image();
imgPresence.Attributes.Add("name", "imnmark");
imgPresence.Attributes.Add("id", "imn1,type=sip");
imgPresence.ImageUrl = "/_layouts/images/blank.gif";
imgPresence.ID = "imgPresence";
container.Controls.Add(imgPresence);

LiteralControl lc1 = new LiteralControl();
lc1.ID = "lcSpan2";
lc1.Text = "
";
container.Controls.Add(lc1);


}

}


That builds our template field. Now for the last little piece. There has to be a way to insert the data from the DataTable in to a variable that we can set in to the image control.
To do that, we need to go in during the data binding of the SPGridView. Which is why I have this line in the code that creates the grid:


grdSPGrid.RowDataBound+=newGridViewRowEventHandler(grdSPGrid_RowDataBound);


That guy calls this method in the same class:


void grdSPGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
string row = "";
//Find the row with the data
DataRowView rowView = (DataRowView)e.Row.DataItem;
row = rowView["Presence"].ToString();
//Create a new image control and cast it as the control in the grid
System.Web.UI.WebControls.Image imgPresence =
(System.Web.UI.WebControls.Image)e.Row.FindControl("imgPresence");
imgPresence.Attributes.Add("sip", row);

}


}


That is all there is to it! It took me nearly a week to figure all of this stuff out. It was a lot of fun finding the right events, and creating the proper event handlers and stuff.

In case you are confused, the code that creates the SPGridView and the code that does the special binding all should be in the same class. The class that creates the ItemTemplate can be in a separate class file, or just as another class in the same class file as your other class.
Hope this helps some people.

Many thanks to Oscar Medina and Ted Pattison. Their posts really helped be out, and I surely would not have gotten my grid to work without them.

4 comments:

Daniel said...

Oscar beats Ted by right hand carpal tunnel mouse attack in the 2nd round.

Natto Ninja said...

Wow, you read that far? Props.

Ben said...

Your a great technical writer. This was not only informative, but cathartic as I realized I was not alone in my pain :)

Natto Ninja said...

Thanks Ben! Hope it helped you out.