Thursday, June 28, 2012

Custom Pager for SPGridView - Part 3

Of course, our pager should be able to allow us to move to the previous page and to the next page. The following method accomplishes this:



Note that here, the target of the postback is the pager, not the gridview. The argument is "nextpage" for the next page and "previouspage" for the previous page. Here is how you call this method from the overridden Render() method:









Finally, our pager should have the dropdown list with the value of possible page sizes. So, we will have to trigger the postback from the <select> element.  This time, it will be trickier because the event argument will not be known until the element is actually selected - so, instead of calling the Page.ClientScript.GetPostBackEventReference() method, we will call __doPostBack() directly. The following chunk of code accomplishes this:



Now, all we have to do is to add our new gridview and pager to the user control of the visual Web Part. Make sure you set the pager's GridViewId property to the Id property of the gridview, and to implement the event handling method for the PageSizeChanged event (and for the PageIndexChanging event, of course.) And, in the OnPreRender() method of your user control, send the initial page size for your gridview (if the request is not a postback one.)

A little gotcha is that if you are putting your web part to a site page, make sure you assign that web part an ID in the <AllUsersWebPart> element. This will ensure your postbacks are always triggered for your gridview, since its client ID will not be changed.

Custom Pager for SPGridView - Part 2

Now that we have an enhanced gridview, we can associate a pager with it. SPGridViewPager comes to mind. The way it is displayed is not exactly what we want, because all it allows you to do is to move to the next page and to the previous page, and it displays the range of rows, not individual page numbers, like this:



There aren't any properties in SPGridViewPager to alter this display style. So, we need to create a new pager class, inheriting from SPGridViewPager.  Unfortunately, overriding the CreateChildControls() method in this class does nothing. So, our only hope is to override the Render() method:














This allows us to generate HTML on the fly, overriding the default rendering.

The problem we are having now is that we will have to trigger a postback from HTML elements. This is not so hard - that's what the Page.ClientScript.GetPostBackEventReference method is for. Here is how we can display individual page numbers (with the number in bold if the page is current, and a link to the correct page for non-current page numbers.)


In this method, a postback is triggered, with the gridview being the event target and "Page$" followed by a number being the event argument. The OOTB SPGridView knows how to raise a postback event for such an event argument.

Here I would like to remind everyone that the Page.ClientScript.GetPostBackEventReference(target,argument) method call is displayed in the HTML source as "javascript:__doPostBack(target, argument)."

To be continued...

Wednesday, June 27, 2012

Custom Pager for SPGridView - Part 1

The problem:

My customer requirement is that the web part has to have a gridview whose pager allows access to any given page, go to the next page, go to the previous page, and select how many rows are displayed per page.

The solution:

This seemingly simple problem is actually a little more complex than it seems. The issue is that SPGridView does not have an event that triggers when the page size changes. So, the first step will be to create a new class that inherits from SPGridView and contains the following three things:

1)  The delegate PageSizeChangeHandler;

2) The public event PageSizeChanged;



3) the overridden RaisePostBackEvent method which triggers the PageSizeChanged event if the event argument is "Rows$" followed by the number of rows (e.g., "Rows$5" for 5 rows.) The dollar sign is used as a separator to follow the guidelines for event arguments for gridviews:



Of course, there should also be a new class PageSizeChangeEventArgs (inheriting from System.EventArgs) with a public property NewPageSize and a public constructor that sets this property based on the argument constructor:


Now we have a gridview capable of raising a postback event when the number of rows to be displayed changes.

To be continued...




Wednesday, June 20, 2012

Capturing Web Analytics Data in a Custom Web Part



The problem:

I need to display the number of daily unique visitors to a site collection on a custom web part. Using the following code results in an array with the only element being "The specified user or domain was not found." The Web application uses claims authentication.


The solution:

What is causing this issue?

The problem is that the GetWebAnalyticsReportData() function takes the user login name from Thread.CurrentPrincipal.Identity.Name and passes it to SPWeb.DoesUserHavePermissions() - but SPSecurity.RunWithElevatedPrivileges does not change Thread.CurrentPrincipal.Identity.Name to the application pool account name. It does, however, change WindowsIdentity.GetCurrent() to the application pool account.

So, it is necessary to explicitly set Thread.CurrentPrincipal to the correct identity. Since my Web application uses claims authentication, here is a correct way to do this:

System.Threading.Thread.CurrentPrincipal = 
 ClaimsPrincipal.CreateFromIdentity(WindowsIdentity.GetCurrent());

Works like a charm!

Tuesday, June 19, 2012

Expanding/Collapsing Web Parts

The problem:

My requirements state that a certain web part on my page should be expandable/collapsible. When the user presses the "-" sign this web part is to collapse, whereas when she presses the "+" sign this web part is to expand. When the page first loads, this web part should be collapsed.

The solution:

We should use jQuery for this purpose. First, we need to find out the ID of the web part in question (it should start with "MSOZoneCell_", something like "MSO_ZoneCell_WebPartctl00_m_MyCoolWebPart") - view your web part page in the source and find the ID there.

Then, add a new Content Editor Web Part to your page. In the CEWP, in the HTML source, type this:

<div align="right" class="content" id="jLoadDiv"><a href="javascript:show(&#39;MSOZoneCell_WebPartctl00_m_MyCoolWebPart&#39;);"><img title="Show" id="plus" alt="Show" src="/_layouts/images/EXPAND.GIF"/></a> <a href="javascript:hide(&#39;MSOZoneCell_WebPartctl00_m_MyCoolWebPart&#39;);"><img title="Hide" id="minus" alt="Hide" src="/_layouts/images/COLLAPSE.GIF" style="display: none"/></a> </div>
<script type="text/javascript">

$("document").ready(function()
{
$('td[id*="MSOZoneCell_WebPartctl00_m_MyCoolWebPart"]').css("display", "none");
$('img[id*="minus"]').css("display", "none");
});
function show(webpartID)
{
$('td[id*="'+webpartID+'"]').css("display", "block");
$('img[id*="plus"]').css("display", "none");
$('img[id*="minus"]').css("display", "block");
}
function hide(webpartID)
{
$('td[id*="'+webpartID+'"]').css("display", "none");
$('img[id*="minus"]').css("display", "none");
$('img[id*="plus"]').css("display", "block");
}</script>

Make sure you set the Chrome to None on your CEWP.

That's it. This does the job. You can even export the CEWP and then add its contents (with the contents of the Content element being HTML-encoded - use  this tool to perform the encoding) to the page or elements file.

Monday, June 11, 2012

DateTime Format With Time Zones

The problem:

In my Visual Web Part, I have to display a current date in the following format:

1:29 pm EDT June 11, 2012

The usual way to display the current date in this format is via <%=DateTime.Now.ToString("H:MM tt K MMMM d, yyyy"). The problem with this approach, though, is that the time zone is displayed in the wrong format, more like

1:29 pm -04:00 June 11, 2012

The solution:

First, we need to find the acronym current time zone. The following two functions take  care of it:


Next, we need to split the formatted date string (where the white space is the delimiter), and replace the second element (i.e., "-04:00") with the proper acronym (i.e., "EDT.") The following code takes care of it:


Finally, in the code-behind for the user control for the Web Part, create a protected method (e.g., ToProperFormat()) and make it return DateTime.Now.ToProperFormat(). Then, in the ASCX file, call <%=ToProperFormat() %> . Note that calling the extension method directly from the ASCX file results in a compilation error unless you import the extension namespace.