About the author

Bart X. Tubalinal

Bart X. Tubalinal is a Sr. Consultant for a company based in the Chicagoland area. His primary focus is on the Microsoft technology stack, including: .NET, C#, SharePoint, InfoPath, Silverlight, Windows Presentation Foundation (WPF), Windows Workflow Foundation (WF), and ASP.NET. He has also dabbled a little in Project Server.

Though he doesn't have as much free time as he would like, when he does, he loves to hang out with friends, try different cuisines, and play with his dog, Tyson.

He also considers himself to be, pound for pound, one of the best developers there is.

Facebook ProfileTwitter: bart_tubalinal

RecentComments

Comment RSS

 

T-SQL Function for Getting Business Dates Between a Given Range

by Bart X. Tubalinal 28. October 2009 14:25

Here's a really simple T-SQL function for getting a list of business dates (Monday - Friday) that fall within a given date range. For example, say I wanted to find the business dates between the dates 10/21/2009 - 10/30/2009. This function will return the following results:

10/21/2009
10/22/2009
10/23/2009
[10/24/2009 is skipped]
[10/25/2009 is skipped]
10/26/2009
10/27/2009
10/28/2009
10/29/2009
10/30/2009

I just thought this function might be helpful for others since I couldn't find anything else out there that someone else had written to accomplish this (easy as it is). There are plenty of T-SQL scripts that return the number of business days between a date range, I just couldn't find one that actually returned the business dates.

Here's the script:

CREATE FUNCTION BusinessDatesInDateRange
(
    @BeginDate DATETIME@EndDate DATETIME
)

RETURNS @BusinessDates TABLE (BusinessDate DATETIME)
AS    BEGIN 
        DECLARE @CurrentDate DATETIME         
        SET @CurrentDate = @BeginDate        

        WHILE (@CurrentDate <= @EndDate
        BEGIN 
            -- 1: Sunday 7: Saturday 
            IF DATEPART(WEEKDAY, @CurrentDate) <> 1 AND DATEPART(WEEKDAY, @CurrentDate) <>
            BEGIN 
                INSERT INTO @BusinessDates(BusinessDate) VALUES (@CurrentDate); 
            END             

            SET @CurrentDate = DATEADD(D, 1, @CurrentDate)             
        END

    RETURN 
    END

GO

Sample call to execute:

select * from dbo.BusinessDatesInDateRange('10/21/2009', '10/30/2009');

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

SQL

Custom SharePoint Designer workflow action for extracting InfoPath attachment fields

by Bart X. Tubalinal 6. June 2009 04:08

I've been meaning to post this for at least a month but I've been so busy with work that I've had no time to sit down and write this until now. But better late than never, I guess.

A little more than a year ago, I developed a browser-enabled InfoPath form that was used on a public-facing MOSS site so that site visitors could submit their photographs to the organization's yearly photo contest. Along with capturing the photo metadata (title, location, date, etc), one of the fields on the form was for the photo itself, captured through the use of the attachment control. The photo contest reviewers wanted to be able to have a quick and easy way to review the photos and the photo metadata so the original thought was to submit the photo to a document library and use property promotion to display the photo along with the metadata. Unfortunately, we quickly found out that the attachment control could not be promoted to a property. So I wrote a web service instead that would accept this form, extract out the attachment, and save the attachment directly into a picture library (and copy the photo metadata as well). That way, the users were able to see the photo and its metadata as soon as they opened the library.

A few months ago, someone again approached me with similar requirements. They were building a form that would have an attachment field that they'd need to extract to put into another library. Well, that was enough right there to make me realize that I needed to build something that would enable users to build these types of solutions themselves. I didn't want to have to keep writing things over and over to support these types of requirements. I also wanted to remove myself out of the process; they were already at 90% of the entire solution since they were building these forms already, they just needed the last 10% of being able to extract the attachment.

So what I ended building instead was a new workflow activity that could be used in SharePoint Designer as a workflow action. Basically, the action can be used as part of a workflow created in SharePoint Designer that will allow the user to add a step in their workflow for extracting out the attachment and saving it somewhere else. When the action is added to the SPD workflow, it looks like the following:

The 'attachment field name' is where the user specifies the name of the attachment control field in the InfoPath form. The link 'the item' is the SharePoint list item this action should work against. This is going to typically be the current item. The 'document library' is where you want the item to be stored to and the 'filename' parameter is the filename to save the attachment as (this can be driven dynamically as well from metadata of the item or some variable). The sentence '(use attachment name instead: Yes)' actually gives you the opportunity to use the actual filename of the file that was attached, rather than having to specify the filename yourself. When a user attaches a document to an InfoPath form, part of what is stored is the actual file's filename. Though that is not available through SharePoint, it can be extracted out by code, which I do inside of the custom action. By specifying 'Yes' for this field, this indicates the action to ignore the filename parameter and use the attachment's filename instead. The workflow designer can also specify whether or not the file should overwrite any existing file in the document library with the same filename. Finally, when the attachment has been extracted and saved, the action returns the item's new id which can be bound to a variable in the SPD workflow.

As an example of how this works, I created a sample New Hire InfoPath Form. The form contains an attachment field for an employee photo. The forms are submitted to my 'New Hire Forms' document library. Here is a sample of the form as it's been filled out:

Now, let's say that as part of this scenario, I want to give others the ability to be able to reuse that employee photo for other purposes (who knows what for, just go along with the example :)). But other people shouldn't be allowed to look at this form because it could potentially hold sensitive information. I need to move this photo out of this form and into another, less-restricted library. So I created a 'Team Photos' Picture Library for this purpose. Now, all I have to do is create a workflow to do the photo extraction. I can create a workflow and attach it to my New Hire Forms document library and configure it to start automatically when a new item is added. Then, I can configure my new workflow as follows:

I have three actions here. The first action is my custom extraction action. I configured it to pull the photo from the 'employeePhoto' field using the current item in the New Hire Forms document library. I save this photo to the Team Photos picture library (and instead of specifying a filename, I'll just use the attachment's filename). I will overwrite the picture if it already exists. Finally, I store the id of the newly added item in the Team Photos library to the workflow variable 'new item id'.

The second action is actually an Update List Item action. I use this action to grab the last name of the employee (from the New Hire Forms item) and save it to the description field of the item I just saved in the picture library. That's why saving the id is important because you can use it to reference to later or make cross references to (which I'll get into in a second). The following is the configuration of this action:

Obviously, I could have created a workflow variable that was a concatenation of the first name, last name, department, etc and used that to fill out the description but this is just for demonstration purposes.

Lastly, the third action is again an Update List Item action. With this action, I update the current item I'm executing this workflow against so that I can add a cross-reference to the photo that I just extracted and stored somewhere else. One of the columns in the New Hire Forms document library is a lookup column called Employee Photo. I configured the lookup column to point to the Team Photos picture library with the referencing being the ID of the picture item. Then, I just configured the action in my workflow as follows:

That's it for the workflow. Finally, when it runs, here's what I get:

In my New Hire Forms library, you can see the workflow has finished executing and there is a link under Employee Photo to the photo that was extracted. And here's the Team Photos picture library with the extracted photo:

 

So, that's pretty much to it. I think from this example, it's easy to see the value of taking some time and really thinking about coming up with custom workflow activities. Custom workflow activities exposed as SharePoinw Designer workflow actions can really enable your users to build their own solutions, possibly in ways you wouldn't have imagined yourself. That's normally how I try to envision solutions. When someone asks me to build something, I ask myself, can I build something else that will allow them to create their own solution now and in the future.

I attached the SharePoint solution file (.wsp) below as well as the VS2008 project files.

In the future, if I see there's enough interest (through comments), I can go into a discussion of the code.

DeviantPointWorkflowActivities.wsp (7.20 kb)   - Once this solution has been deployed, you need to activate the DeviantPoint Custom Workflow Activities web application feature. Repeat - web application feature, not site collection, not site.

DeviantPoint.SharePoint.Workflow.zip (499.49 kb)

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

SharePoint Designer Custom Workflow Activity - WTF

by Bart X. Tubalinal 2. April 2009 09:05

I was writing a custom workflow activity for SharePoint Designer today (a pretty cool custom activity, if I do say so myself, which I'll post in a few days) and on my custom activity, one of the settable fields I have happens to be the last word in the sentence:  

So I went to go set the field and this is what I get:


Whenever I went to set the field, the hover action over the activity added a drop down button to the end, thereby making the field impossible to set. Anyways, I'm just going to change the sentence but I thought it was pretty funny and wanted to share it.

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Using the jQuery UI Dialog widget for confirmation windows

by Bart X. Tubalinal 12. March 2009 09:09

In our web applications, we often have the need to confirm with the user whether or not they want to proceed with an action they attempted to take. For instance, we might have a delete button on our form that responds to a user click by deleting a record in the database. Before we actually do the delete, we want to double-check with the user first. JavaScript has a confirm(msg) function that will display a standard dialog window that you can use to determine whether or not to continue with an action. The standard dialog window looks like the following (in IE): 

The standard confirm window is not very flexible. First, you only get two buttons (and no more), Ok and Cancel, and you can't change the text of either button. Second, you can't change the title on the title bar. You also can't change the question mark icon inside of the dialog. If you need something more flexible, you're going to have to create your own modal overlay window and recreate the functionality that confirm() gives you. Fortunately, jQuery UI has a dialog widget that will get you most of the way there:

 

As you can see, this dialog window definitely doesn't look like the standard confirm. First, I have more than two buttons and with different text. I also have a different title. It can be resized and repositioned. Also, although you can't see it, this dialog window can have the same visual effects (like applying semi-transparent overlays on everything underneath the window) applied to it that can be applied to other html elements by using jQuery. This would not be possible with the standard confirm. I won't bore you with the details of how to set this up. The documentation on the jQuery site sufficiently explains that. What I want to concentrate on is showing you how to make the jQuery dialog widget behave exactly like the standard JavaScript confirm.

When using the standard confirm, all processing stops until the user clicks on either Ok or Cancel in the window. That means that if the button was supposed to post back to the server, the postback won't occur until a selection is made. Usually, we only go ahead with the postback if the Ok button was clicked. This is typically handled by adding something similar to the OnClientClick event handler on our button:

<asp:Button ID="Button1" runat="server" Text="JavaScript" OnClick="Click" OnClientClick="return confirm('Dude, are you sure?');" />

If the user clicked the Ok button, true would be returned by the confirm(msg) function. If the user selected Cancel, false would be returned. Returning false in OnClientClick will effectively cancel the rest of the event processing (i.e., the event handler for the OnClick event won't be triggered).

Now, for the jQuery dialog, this is how I have the button:

<asp:Button ID="Button2" runat="server" Text="jQuery" OnClick="Click" OnClientClick="showjQueryDialog();return false;" />

I call a client-side function showjQueryDialog() which, amazingly enough, handles showing my jQuery dialog (pre-configured in the document ready event handler). After the call to showjQueryDialog(), I go ahead and just return false. The reason why I have to return false here is because, unlike the confirm() function, the browser doesn't stop processing the rest of the client-side script just because the dialog widget is opened. So we have to manually stop it. Now this raises an issue. So if we always return false, how will our event handler for the OnClick event ever be executed? In order for that event handler to execute, we will need to do the postback ourselves. There are a couple of ways to handle this but this is the approach I take:

1. Create a hidden field on the form (called hdnBtnPostback) who's value will be the exact postback function call I need to make in order to emulate the same postback that would occur had the button processing continued. This value can be set in the Page_Load() of the ASP.NET page. But how do we know what the exact postback function call should be? Fortunately for us, that is one of the methods available from the ClientScriptManager object. So the following code will do the trick:

 

this.hdnBtnPostback.Value = Page.ClientScript.GetPostBackEventReference(Button2, string.Empty);

 

This is will generate the exact same call to the __doPostBack() JavaScript function that is generated by the button. 

2. In the event handler for my dialog's Ok button click, all I have to do then is get the value of this hidden field and pass it to the JavaScript eval() function. This will effectively execute the postback and the event handler for the OnClick event will be processed: 

    1    function showjQueryDialog() {

    2 

    3       $("#dialog").dialog("open");

    4    }

    5 

    6    //document on ready.

    7    $(function(){

    8       $("#dialog").dialog({

    9          autoOpen: false,

   10          modal:true,

   11          buttons : {

   12             "Yes" : function() {              

   13                $(this).dialog("close");

   14                eval($("#<%= hdnBtnPostback.ClientID %>").val());

   15             },

   16             "No" : function() {

   17                $(this).dialog("close");

   18             },

   19             "Maybe": function() {

   20                $(this).dialog("close");

   21                //what should we do when "Maybe" is clicked?

   22             }        

   23          }

   24       });

   25    });

The event handler for the Yes button in the dialog are on lines 12-15. Line 14 is where we actually do the postback manually. 

As you can see, it is quite easy to replace the standard confirm dialog and make it function in the same way. All that is needed is a little elbow grease :).

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 4 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

ASP.NET | jQuery | JavaScript

Creating a SharePoint breadcrumb control with drop down menus

by Bart X. Tubalinal 3. February 2009 14:05

While there have plenty of things that people don't like about Windows Vista, one of the features I do like and I think is underappreciated is the new breadcrumb:

Unfortunately in SharePoint, the breadcrumbs don't work like this. There are no submenus. Lucky for us, it's pretty easy to create a breadcrumb that functions this way. Here's how:

Creating the web control

The first part we'll need is a web control that will render the markup. The web control I created is simple and straightforward. Only two public properties are available: SiteMapProvider (string) and NodeSeparator (string). The SiteMapProvider property is used to specify the named site map provider from the web.config file to use to build the breadcrumb. The NodeSeparator property is used to specify the character(s) to use to separate each item in the breadcrumb (like the sideway triangles in the screenshot above). By default, this is set to the '>' character. Also, this property will only be used if the breadcrumb item doesn't have any sub-items; if it does, then a clickable image like the one above will be displayed instead. Lastly, the breadcrumb's submenus will only go one level deep (like Vista's). So there won't be any "submenus of submenus".

The rendering logic takes place in the RenderContents(HtmlTextWriter) method. First, we get an instance of the SiteMapProvider object that has the same name as the one specified in the control's SiteMapProvider property. Then, using the provider, we traverse our way up the site map from the current page (specified by the SiteMapProvider.CurrentNode property) until we get to the top of the site map. While we traverse up, we will collect the nodes that we passed in a Stack<SiteMapNode> collection:

            SiteMapProvider provider = GetSiteMapProvider();

 

            Stack<SiteMapNode> nodes = new Stack<SiteMapNode>();

 

            SiteMapNode current = provider.CurrentNode;

            while (current != null)

            {

                nodes.Push(current);

                current = current.ParentNode;

            }

 

Once we've reached the top, we then pop the nodes out of the stack. Each node will be represented as a list item (li) in an unordered list (ul). If the node has any subitems, then we will render a clickable image and unordered list that is a child of the list item:

            while (nodes.Count > 0)

            {

                SiteMapNode node = nodes.Pop();

                sb.AppendFormat("<li class='dp-breadcrumbitem'><a href='{0}' title='{1}'>{1}</a>", node.Url, node.Title);

 

                //why not use SiteMapNode.HasChildNodes? see: http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopment/thread/37d10f92-140f-4ce8-b71c-388163721737/

                if (node.ChildNodes.Count > 0)

                {

                    sb.Append("<img src='/_layouts/images/marr.gif' class='dp-breadcrumbitemimage'/>");

 

                    sb.AppendFormat("<ul id='dp-submenu-{0}' class='ms-topNavFlyOuts dp-breadcrumbsubmenu'>", node.Key);

                    foreach (SiteMapNode subNode in node.ChildNodes)

                    {

                        sb.AppendFormat("<li class='dp-breadcrumbsubmenuitem'><a href='{0}' title='{1}' class='dp-submenulink'>{1}</a></li>", subNode.Url, subNode.Title);

                    }

                    sb.Append("</ul>");

                }

                else

                {

                    if (nodes.Count > 0) sb.AppendFormat("<span class='dp-breadcrumbseperator'>{0}</span>", nodeSeparator);

                }

                sb.Append("</li>");

            }

 

This is the typical html markup that is rendered by the breadcrumb control:

<ul class='dp-breadcrumb'>

    <li class='dp-breadcrumbitem'><a href='link' title-'Item 1'>Item 1</a> <img src='path_to_image' />

        <ul class='ms-topNavFlyOuts dp-breadcrumbsubmenu'>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 1' class='dp-submenulink'>Sub Item 1</a></li>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 2' class='dp-submenulink'>Sub Item 2</a></li>

        </ul>

    </li>

    <li class='dp-breadcrumbitem'><a href='link' title-'Item 2'>Item 2</a> <img src='path_to_image' />

        <ul class='ms-topNavFlyOuts dp-breadcrumbsubmenu'>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 3' class='dp-submenulink'>Sub Item 3</a></li>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 4' class='dp-submenulink'>Sub Item 4</a></li>

        </ul>

    </li>

</ul>

 

That is pretty much it for the web control. The next parts we need to build are the CSS and the JavaScript.

CSS and JavaScript

I won't bore you with the details of the CSS since it's available in the zip file below but the most important things with the CSS are: making sure the breadcrumb items are displayed inline and making sure the submenu uses absolute positioning and has a z-index that will place it on top of any other element that is going to be on the page.

Now for the UI magic to happen, we need a little client-side code. Here again, I use my new favorite client-side library, jQuery. The client script primarily consists of three event handlers: an event handler for the click event on the image, an event handler for a submenu item's hover event, and an event handler used to handle clicks anywhere else on the document.

The click event for the image is used to show/hide the corresponding submenu. It will also make sure that no other menu is visible besides the one that corresponds to the image that was clicked. Finally, it will toggle an appropriate image to use, depending on whether or not the submenu is visible or hidden:

    //add the event handler for the click on the image

    $("img.dp-breadcrumbitemimage").click(function(e){   

        e.stopPropagation();

 

        var theImage = $(this);

 

        var left = this.offsetLeft + 12;

        var top = this.offsetTop + 12;

 

        //get the submenu corresponding to the image.           

        var submenu = theImage.next("ul.dp-breadcrumbsubmenu");

 

        //iterate over all the submenus in this breadcrumb and hide any that isn't the target submenu.

        $("ul.dp-breadcrumbsubmenu").each(function(idx){           

            if (this.id != submenu.attr("id")){

                $(this).hide();

                $(this).prev("img.dp-breadcrumbitemimage").attr({src:"/_layouts/images/marr.gif"});

            }

        });

 

        //if the target submenu is visible, hide it. if it's invisible, show it.

        //also change the image that is being displayed.

        if(submenu.is(":visible"))

        {

            theImage.attr({src : "/_layouts/images/marr.gif"});

            submenu.slideUp();

        }

        else

        {

            theImage.attr({src : "/_layouts/images/menu2.gif"});

            submenu.css("position", "absolute").css("top", top).css("left", left).slideDown();

        }       

    });

The hover event handler for each submenu item will take care of making sure the item is highlighted properly. The highlighting we will use is the same highlighting used by the top navigation menu:

    //add a hover event for the items in the submenu so that they are highlighted.

    $("li.dp-breadcrumbsubmenuitem").hover(function(){

        $(this).addClass("ms-topNavFlyOutsHover");

    },

    function(){

        $(this).removeClass("ms-topNavFlyOutsHover");   

    });

Lastly, the click event handler for the document will make sure that if the user clicks outside of the breadcrumb, any visible submenu will be hidden:

    //add a click handler for the entire page so that when the user clicks outside of the breadcrumb, any visible menu will be hidden.

    $(this).click(function(){

        $("ul.dp-breadcrumbsubmenu").slideUp();

        $("img.dp-breadcrumbitemimage").attr({src:"/_layouts/images/marr.gif"});

    });

 

Putting it all together

So now that we've built the components, it's time to put it all together. First, add the breadcrumb.css file and breadcrumb.js file in a document library or folder in the SharePoint site collection. You also need to place the jQuery javascript library in a document library/folder (note: I used jQuery version 1.2.6 for this).

Second, you need to deploy the assembly 'DeviantPoint.SharePoint.Web.UI.dll' to the Global Assembly Cache (GAC) and then reset IIS.

Third, in order to be able to use the web control we developed, you need to add an entry into the SafeControls section of your web.config file:

      <SafeControl Assembly="DeviantPoint.SharePoint.Web.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d767c3b2d2145e42" Namespace="DeviantPoint.SharePoint.Web.UI.WebControls.Navigation" TypeName="*" Safe="True" />

Lastly, you need to modify your site collection's master file by first registering our custom assembly:

<%@ Register Tagprefix="DeviantPoint" Namespace="DeviantPoint.SharePoint.Web.UI.WebControls.Navigation" Assembly="DeviantPoint.SharePoint.Web.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d767c3b2d2145e42" %>

Then you need to add a reference to the css and javascript files we are using:

    <!-- reference to the breadcrumb css -->

    <link href="/Style Library/Breadcrumb.css" type="text/css" rel="stylesheet"/>

 

    <!-- add the reference to the two jquery libraries -->

    <script src="/scripts/jquery-1.2.6.min.js" type="text/javascript" language="javascript"></script>

    <script src="/scripts/Breadcrumb.js" type="text/javascript" language="javascript"></script>   

Note that in this case, I put 'Breadcrumb.css' in the default Style Library document library. I put the javascript files in a folder I created with SharePoint designer called scripts. Also, make sure to place these lines right before the closing 'head' tag in the master file.

Lastly, we need to place the breadcrumb control in an appropriate spot on the master page where we'd like the breadcrumb to appear:

              <asp:ContentPlaceHolder id="PlaceHolderGlobalNavigationSiteMap" runat="server">

                <!-- DEVIANTPOINT breadcrumb -->

                <DeviantPoint:Breadcrumb ID="bcGlobal" runat="server" SiteMapProvider="GlobalNavSiteMapProvider"></DeviantPoint:Breadcrumb>

              </asp:ContentPlaceHolder>

In this case, I actually replaced the default global breadcrumb that comes out of the box with my own breadcrumb. I also used the GlobalNavSiteMapProvider because that site map provider will have all of the nodes for the site collection.

Results

Here are some screenshots of the new breadcrumb in action:

 

 

See? Not too difficult!

Files

In the zip file below, you'll find the assembly that needs to be deployed to the GAC, Breadcrumb.cs, breadcrumb.css, breadcrumb.js, and deviantpoint.master. Deviantpoint.master is just the out of the box master with all the changes to the master file I specified above. You will need to download jQuery v. 1.2.6 yourself.

DeviantPoint_Breadcrumb.zip (11.12 kb)

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 4.8 by 4 people

  • Currently 4.75/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

JavaScript | jQuery | SharePoint | SharePoint Development

Creating an accordion-style SharePoint Quick Launch menu with jQuery

by Bart X. Tubalinal 16. January 2009 08:39

About two months ago, a colleague of mine told me of a requirement he had to build an accordion-style menu for an intranet MOSS portal he was building. Not knowing where to start, he asked me what he could do. I advised him that he could either try to work with the <SharePoint:AspMenu> control used by the Quick Launch out of the box or he could use the Accordion control found in the AJAX.NET Control Toolkit and (combined with the SharePoint navigation providers), he could roll his own menu control. They never got around to actually doing this as more critical requirements came about and they eventually postponed this accordion-style menu to a later phase.

Last weekend, I finally decided to look into the examples and documentation around jQuery. I had heard of it before but never really had a chance to play around with it. But I decided that it would be a worthwhile investment of a few hours to learn it, especially since I had read a few weeks ago that Microsoft had put it full jQuery support into Visual Studio 2010. What I found after a few hours of digging in impressed me. I can see why everyone seems to be buying in and using jQuery. It simply makes client-side, rich internet application development much simpler. I have never enjoyed writing Javascript (frankly, I've tried to avoid it as much as possible) but with jQuery, it makes it so easy.

So, being as excited as I was, I started to dream up some ideas I had on how I could use jQuery. I have kicked around a few ideas with some of my colleagues and acquaintances and the ones they thought were good ideas, I will start to build during the upcoming days and months. I will release the code either here (if it is just small snippet code) or in CodePlex if I build out libraries or toolkits. But yesterday, as I was laying in my bed freezing (it was 45 degrees in my house because my furnace went out and had to be repaired this morning), I thought about the accordion control my colleague mentioned. I thought to myself, now this is something I think could do quickly with jQuery. So today, as I waited for the service guy to fix my furnace and for my new treadmill to be delivered, I wrote some jQuery code to make the SharePoint Quick Launch menu work like an accordion. 

Getting Started

By default, the following image is the default appearance of the Quick Launch menu in my dummy test portal: 

I annotated the image in order to better explain what I did. First, in order to understand the javascript I wrote, it is important to understand the html output that is emitted by the Quick Launch menu. Every site in the menu is outputted as a table (item B). That table has the CSS class 'ms-navheader' applied by default (I say by default because you can always override the CSS class assigned in the <SharePoint:AspMenu> control; all references to CSS classes here are in reference to the default CSS classes assigned out of the box). Within this table, a link (item A) is added to the actual site. Each of these anchor tags also have the 'ms-navheader' CSS class applied to it. Lastly, each sub-menu section (item C) is another table with the CSS class 'ms-navSubMenu2' applied.

My accordion would function as follows (the UI requirements, if you will):

  • Only one sub menu at a time should be visible.
  • If you click on a site's navigation bar (item B), the sub menu (item c) should appear and all other sub-menus would disappear.
  • If you click on a site's navigation bar to attempt to expand the sub-menu but the site has no sub-items, nothing should happen (the currently visible sub-menu should still remain).
  • Clicking on the site link (item A) should still continue to take you to the site.
  • By default, when the page is first rendered, the current site the user is on should have its sub-menu expanded out.

The following zip file contains the javascript I wrote to satisfy these UI requirements (the zip also contains both the minified and Visual Studio annotated 1.2.6 version of jQuery): quicklaunch_accordion.zip (58.59 kb)

Some highlights and explanations of the code: 

Finding each sub menu 

//For each Quick Launch navigation sub menu:

    $("table.ms-navSubMenu2").each(function(){

        //Find any navigation items under the sub menu that have been selected.

        var selectedNavItems = $(this).find("a.ms-selectednav");

 

        //Find the corresponding navigation header of the current sub menu being processed

        var menuHeader = $(this).parents("tr:eq(0)").prev("tr").find("table.ms-navheader:eq(0)");           

 

        if ($(menuHeader).hasClass("ms-selectednavheader") || selectedNavItems.length > 0)

        {

            //if the navigation header for this sub menu is selected or if there are any

            //selected navigational items in this submenu, show the submenu.

            $(this).show();

        }

        else

        {

            //otherwise, hide the submenu

            $(this).hide();

        }

    });

When the page is first rendered, the above snippet finds each sub-menu and automatically hides or displays them based on the UI requirements above. A sub-menu item link that is selected will have the 'ms-selectednav' CSS class applied. Similarly, if the user is currently on the welcome page of a site that appears on the Quick Launch, the 'ms-selectednavheader' CSS class will be applied to the corresponding table element in the Quick Launch html.

Handling a site's link (item A) click event

//When a user clicks a navigation header, the user should be taken directly

//to the site link. The javascript event handler to hide/display the submenus

//should not be triggered.

    $("a.ms-navheader").click(function(e){

        e.stopPropagation();

    });

 

Normally, the javascript that is triggered when clicking on the table navigation header (item B) would execute because this anchor tag is within that table. Calling stopPropogation() prevents that click event on the table from firing.

 

Event handler for site menu items

 

//Finally, this adds a click event handler for the navigation header table

    $("table.ms-navheader").click(function(e)

    {           

        var subMenu = $(this).parents("tr:eq(0)").next("tr").find("table.ms-navSubMenu2:eq(0)");

        if (subMenu.length > 0)

        {

            //only if we have a submenu should we hide the other submenus and show the current one.

            $("table.ms-navSubMenu2").hide("slow");

            subMenu.show("slow");

        }

 

    });

The above snippet adds an event handler to handle clicks on the table. And the final output of this code is:

Portal home page (on first load) 

 

Portal home page (News expanded) 




Portal Home Page (Search expanded)

Portal Home Page (Sites expanded)

News Home Page

Sub link Page




Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 6 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

JavaScript | jQuery | SharePoint Development

Configuring SSO for MOSS with split-farm back-to-back topology

by Bart X. Tubalinal 8. December 2008 14:09

I had to configure SSO on a MOSS farm with split back-to-back topology with the Web Front Ends (WFEs) in the perimeter network belonging to one domain and SQL Server in the internal network belonging to another domain (there was a trust relationship defined between the two domains). All the service accounts, farm accounts, and database access accounts being used came from the internal domain.

When I tried to configure the SSO service (ssosrv.exe) to use an account from the internal domain, I would get the following error:

A Single Sign-on error has occurred.  Please contact an administrator.  Details: The network path was not found.

There would also be a corresponding entry in the event log like this:

User internaldomain\internaluser failed to configure the single sign-on server. The error returned was 0x80630005. Verify this account has sufficient permissions and try again.

The account used absolutely had sufficient permissions. It met all the criteria specified in http://technet.microsoft.com/en-us/library/cc262932.aspx for the ssosrv.exe service account. I tried and re-tried with other accounts, making the account a domain admin, giving it every SQL Server permission known to man (jk) and it still didn’t work. I even manually created the SSO database myself and ran the SSO schema script myself and manually added permissions to it but I still couldn’t get SSO configured in Central Administration. Actually, I had ruled out issues with SQL Server permissions earlier because when I checked the SQL Server logs, there weren’t even any log entries for login failures. So I sort of came to the conclusion that something else was happening on the WFE before it even got to the database.

After struggling with the configuration for a while and looking at many error log entries, I finally got it configured. Looking at the ULS logs actually helped me resolve the issue because I noticed the following entry:

Net{User|Group}GetInfo said Account {internaluser} does not exist   

What a strange error. Clearly, SSO configuration was attempting to use the command-line command NET USER internaluser to get information about the account being used by ssosrv.exe. But issuing that command goes against the current domain of the machine, which in this case was the domain of the perimeter network and not the internal network domain where this account really came from. By switching the account used by ssosrv.exe to an account that instead came from the perimeter domain and adding that account to SQL Server (and making sure it conformed to the criteria as outlined in the article above), I was able to successfully configure the SSO.

Just another example where the error displayed hardly gets you down the right path.

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 3.5 by 2 people

  • Currently 3.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

SharePoint Configuration | SharePoint

Issue tracking permissions issues with Project Server

by Bart X. Tubalinal 23. November 2008 14:05

A client wanted to be able to secure specific items in the Issues list of a Project Workspace site so that only a certain group could view the item. No problem, right? Just use the out of the box item-level permissions functionality in SharePoint.

Not so fast. This works well until you start linking a project task to one of these issues. If a project task is linked to an issue or issues for which a user does not have permissions for, then the user will get the typical Access Denied SharePoint error page when the user tries to view the task's linked issues page. So far, so good. Unfortunately, if the task is linked to multiple issues, some of which the user has permissions to view and some of which the user cannot view, the same Access Denied SharePoint error page will be shown. You would think that the user wouldn't get this error page and instead would get a list of just the items he/she has access to but unfortunately not. Hopefully, this will be fixed in the future by Microsoft.

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 3.0 by 3 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

SharePoint | SharePoint Security

When to dispose of the SPSite.RootWeb object

by Bart X. Tubalinal 14. August 2008 22:08

A few weeks ago, I was doing some code reviews when I ran into the following snippet of code:

Example A
SPWeb web = new SPSite(siteGuid).RootWeb;

I told the developer who wrote the code to dispose of these objects he created per the suggestions in the Best Practices: Using Disposable Windows SharePoint Services Objects article. The next morning, the developer sent me an email pinpointing a piece of code he thought I wrote that to him seemed to be the same type of code as his where the objects weren't being destroyed. This was the piece of code:

Example B
SPWeb web = SPContext.Current.Site.RootWeb;

So what was the deal here? Was this a case of "do as I say, not as I do"? No, it wasn't. First of all, the code he was citing wasn't actually written by me, it had been written by another developer. I was, however, the reviewer. Second, I was concerned not only with the web object in his code but the SPSite object he "news up". Both of those objects need to be destroyed which he wasn't doing. At the very least, Example A code should have been rewritten as:

SPSite site = new SPSite(siteGuid);
SPWeb web = site.RootWeb;
//do stuff
web.Dispose();
site.Dispose();

One thing you'll notice here is that I keep a reference to the SPSite object that is being created. That is because this object will later need to be disposed of and by not keeping a reference to this object, there is no way we would be able to do that.

Now back to the RootWeb object, which this developer was pointing out to me also wasn't being disposed of in Example B code. So why not? Is this bad practice not to dispose of the RootWeb object? If you read the best practices article, it clearly states that the SPSite.RootWeb property creates a new object and assigns this new instance to a local member variable. Each subsequent access will be referencing the object stored in the local member variable. Basically, the RootWeb object is lazy loaded on first access. The article later warns that after you're done using the RootWeb property, you should dispose of it. Clearly, Example B doesn't do that. However, not everything is as straightforward as this article suggests.

In the example code in the article, the RootWeb object being accessed is from an SPSite object that was created explicitly. In Example B above, the RootWeb object is being accessed from the SPContext.Current.Site object. As most of you know, the Site and Web properties from SPContext.Current are special in that the disposal of these objects are managed by SharePoint and should not be disposed of explicitly. So what happens then if you dispose of RootWeb from SPContext.Current.Site? Will you get the dreaded "Trying to use an SPWeb object that has been closed or disposed and is no longer valid" error message you might get if you disposed of the Site or Web objects? No you wouldn't. That's because the getter method of the RootWeb property checks to see if the local member variable web object is null. If it is null, then the root web object will just be created and it will be assigned to a local member variable (the lazy loading described above). When the RootWeb is disposed, the reference is set back to null. So any subsequent calls to the property after RootWeb has been disposed of will just reconstruct the object again. So you could dispose of it if you want. But for performance reasons, my suggestion is you shouldn't.

The particular line of code in Example B is actually inside a method in a class that was created to handle a custom localization solution. Potentially, this method could be called 20-25 times per page request. If the RootWeb.Dispose() method is called within this method, then that would mean we would be creating and destroying the same object 20-25 times per page request. That will affect performance. But if I'm suggesting that you don't explicitly dispose of this object you should be asking yourself if we are then creating a memory leak. The answer is no.

When an SPWeb object is created, a reference to this object is added to an internal list of SPWeb objects maintained in the SPSite object to which the SPWeb object belongs. When the SPSite object is disposed, all SPWeb objects in this internal list (m_openedWebs) are iterated over and have their Dispose() methods called also. Since a reference to the root web is added to this internal list of the SPContext.Current.Site object, even though we don't call the SPContext.Current.Site.RootWeb.Dispose() method explicitly, the RootWeb object will eventually be disposed when SharePoint has finished the request and disposes SPContext.Current.Site for us. By following this suggestion, RootWeb will be created and destroyed only once per page request rather than the 20-25 times it would have been had we called RootWeb.Dispose() explicitly.

If you're interested in developing on the SharePoint platform, these are some of the considerations you need to be aware of. As I mentioned in my previous blog entry, in order to really write good SharePoint code, it is important to know the SharePoint object model's implementation. Otherwise, it is very easy to write inefficient code even while we believe we're following all the guidelines and the best practices.

 

UPDATE: It looks like the Best Practice document has been updated. It is now NOT RECOMMENDED for you to dispose the RootWeb directly. Looks like they've acknowledged that this is being handled internally, as I mentioned above. This is the text from the MSDN article: 

SPSite.RootWeb Property

An earlier version of this article indicated that the calling application should dispose of the SPSite.RootWeb property just before disposing of the SPSite object that is using it. This is no longer the official guidance. The dispose cleanup is handled automatically by the SharePoint framework. Additionally, SPSite properties LockIssue, Owner, and SecondaryContact used the RootWeb property internally. Given the updated guidance for RootWeb, it is no longer advisable to call the Dispose method on the SPSite.RootWeb property whenever any of these properties are used.

 

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

SharePoint | SharePoint Development

The SharePoint Development Challenge

by Bart X. Tubalinal 8. August 2008 00:00

I was refactoring some SharePoint code today when I ran into a situation that I think illustrates the challenges with developing in SharePoint. Since object creation and destruction are some of the more expensive operations in SharePoint, I was looking through the code for places where SPSite and SPWeb objects were being unnecessarily created and destroyed. There were a lot of places in the code that followed this typical pattern:

using (SPSite site = new SPSite("http://rootsite")) 
{ 
  using (SPWeb web = site.OpenWeb()) 
  {
    //Do something ....
  }
} 

Now this isn't bad. The site object and the web object are properly wrapped in a using statement so these objects will be properly disposed after we're done using them just like the best practices tells us to do. But in a lot of the places where this type of code was written, it was unnecessary to create brand new site and web objects. We could have just as easily used the SPContext.Current.Site and the SPContext.Current.Web objects. So that's what I started to do. Any code where SPSite and SPWeb objects were being created and destroyed, I refactored to use SPContext.Current.Site and SPContext.Current.Web if it was possible to do so. Again, following best practices, I removed the using statements and made sure not to explicitly call Dispose() on these objects since SharePoint will take care of disposing these objects, unlike the SPSite and SPWeb objects created explicitly.

Every once and a while, I would rebuild, redeploy, and retest the code to make sure nothing was broken. Everything seemed to be going swimmingly. However, after refactoring a particular web part, I started to get the dreaded "Trying to use an SPWeb object that has been closed or disposed ..." error. I checked and re-checked the code. I didn't explicitly call Dispose() on any object in the web part, let alone any SPWeb or SPSite objects. I didn't have any SPWeb or SPSite objects wrapped in a using statement either. So what gave? What was breaking? The code didn't spit out this error before so why was I getting this error now after the refactoring?

I started to go over the places where I changed the code when this particular snippet in the code caught my eye:

using (FullTextSqlQuery sqlQuery = new FullTextSqlQuery(SPContext.Current.Site))
{
    //Do something ...
}

 

So using Reflector, I disassembled the FullTextSqlQuery class to see what the heck was going on. What I found was that when you construct a new FullTextSqlQuery object (and other objects that derive from the Query object), the SPSite object passed into the constructor is saved to an instance variable (m_site). And when you dispose of your FullTextSqlQuery object instance, it's Dispose() method calls m_site.Dispose(). Since I passed in the SPContext.Current.Site object, when some other code in the request pipeline needed to use this Site object, an error occurred because it had already been disposed.

So the lesson is if you're using the classes derived from the Query class (from either the Microsoft.SharePoint.Search.Query or the Microsoft.Office.Server.Search.Query namespaces) and you use the constructor that accepts an SPSite object, then be careful not to pass an SPSite instance that is needed after the Query object is destroyed. That means never pass it the SPContext.Current.Site object nor any SPSite object you construct yourself that you'll need after the Query object's lifetime.

Now back to my original point: This is simply another example of what can make SharePoint development difficult. When using the SharePoint object model, it is often necessary to know the implementation of the SharePoint object you're trying to use otherwise you may not know how to handle the object properly (as in the case of disposing SPWeb and SPSite objects) or may not know of the side effects introduced by the object. In this case, I had to crack open the code for the FullTextSqlQuery class in order for me to see what side effects occurred when using this class. And this is not the first time I've had to do that (I'll post about another case in a future blog).

I think that SharePoint is a fantastic product and definitely a viable development platform. In my opinion, the benefits and features of SharePoint far outweigh the challenges the product can sometimes present (from a development perspective). But API/Framework developers should strive to avoid forcing their consumers to have to know the inner workings of their framework in order to use it correctly and in this regard, I think there is still some room for improvement in SharePoint.

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

SharePoint | SharePoint Development