It's ok. Be different.

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:ButtonID="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).

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 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:

1function 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 :).

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

//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