Thursday, August 27, 2009

Abstract Class vs. Interface

While the differences between abstract classes and interfaces are varied, this is what helps me to remember when to use which:

An interface describes the peripheral abilities of a class, not its central identity.
An abstract class defines the core identity of its descendants.

source

Thursday, April 09, 2009

Referencing jQuery on a masterpage without breaking intellisense

If you are like me, you were pretty excited when Microsoft added support for jQuery to intellisense in visual studio. I am not going to go into how this is accomplished. If you would like details you can read Scotts blog here.

The Problem
When referencing jquery through a masterpage, you are vulnerable at runtime to the path of the content page. So if you keep your masterpages in a folder like "/Masters" but the content page you are using is in the root, a jquery path like this won't work:

<script src="../js/jquery-1.3.2.js" type="text/javascript"></script>

Intellisense works fine with this, but at runtime the path is wrong when using the content page in the root. To get the correct path at runtime we can do something like this:

<script src="<%= VirtualPathUtility.ToAbsolute("~/js/jquery-1.3.2.min.js") %>" type="text/javascript"></script>

With that reference we get the correct reference at runtime, but we get no intellisense.

The Solution
Until this problem is fixed by Microsoft, we need to use the best of both worlds. A static reference for the IDE, but a dynamic reference at runtime. To do this we use a condition on the reference that we don't want at runtime, like this:

<script src="<%= VirtualPathUtility.ToAbsolute("~/js/jquery-1.3.2.min.js") %>" type="text/javascript"></script>
<% if (this == null) { %>
    <script src="../js/jquery-1.3.2.js" type="text/javascript"></script>
<% } %>

You can replace the "this == null" with a simple "false" but you will get unreachable code warnings. You just need anything that always evaluates to false. This works great and has very little overhead in your page. I am happy that Microsoft added intellisense support for jQuery, now they just need to finish it so we don't have to use kludges like this.

Tuesday, April 07, 2009

'DropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

This error occurs when the SelectedValue property of a DropDownList is being set to a value which does not exist in the DropDownList. There are various reasons this can occur. In my case, the cause was that a record in the database was referring to a user configurable "category" that had been deleted by the user. Here are three possibilities for handling the scenario. They differ mostly in style, so pick the one that suits your taste.

All of these examples use a DropDownList set up like this:

<asp:DropDownList runat="server" ID="ddlCategory" DataSource='<%# GetCategories() %>'   
    DataTextField="Value" DataValueField="Key" AppendDataBoundItems="true">
    <asp:ListItem Text="(none)" Value="" />
</asp:DropDownList>

Option 1:



SelectedIndex='<%# Eval("CategoryID") is DBNull ? -1 : ddlCategory.Items.IndexOf(ddlCategory.Items.FindByValue(Eval("CategoryID").ToString()))  %>'



Here, I use the ternary operator to determine the index to select. If the database value is DBNull then -1 is used as the index, otherwise a search of the existing list items is performed to determine where in the list the desired value is. The great thing about this is that if the item is not DBNull but it's not in the list of items either, IndexOf() will return -1!

Option 2:



SelectedIndex='<%# GetSelectedCategoryIndex(ddlCategory, Eval("CategoryID")) %>'



This passes the DropDownList and the desired value to a method. There you can use the provided information to determine which index to use and handle any errors that may occur. The method should return an int value.

Option 3:


aspx:

OnDataBound=ddlCategory_DataBound



C#:
protected void ddlCategory_DataBound(object sender, EventArgs e)
{
    ddlCategory = sender as DropDownList;
    //set the selected index.  If the CategoryID is not in the list, set index to 0
    ddlCategory.SelectedIndex = 0;
    try
    {
        ddlCategory.SelectedValue = ((GetDataItem() as DataRowView).Row["CategoryID"]).ToString();
    }
    catch { }
}

Friday, February 13, 2009

Using 'esc' to close dynamic modal dialogs

In my previous post I described how to get a reference to a behavior when you know the target control (speaking in terms of the Ajax Control Toolkit extenders) of the extender. This is necessary to be able to call the hide() method of the ModalPopupExtender when the 'esc' key is pressed.

With this knowledge and jQuery we can get a handle on each of the ModalPopupExtender behaviors in a databound repeating control (such as GridView or Accordion.)

jQuery can be used to get a set of DOM objects by class. I want to get the set of DOM objects that represent the target controls of all the ModalPopupExtenders in the page. In order to facilitate this, I set the CssClass property of all the target controls to "mpeTarget". It's important to remember that it's possible to set multiple classes for the same element by separating them with a space, so if your target control already has a class assigned to it you can still add "mpeTarget". Your class or CssClass property would then look like CssClass="myExistingClass mpeTarget"

Now that all the target controls have been given a CssClass to distinguish them, we are ready to write some javascript.

        function pageLoad(sender, args) {
            if (!args.get_isPartialLoad()) {
                $addHandler(document, "keydown", onKeyDown);
            }
        }


This sets up the document to respond to the "keydown" event by calling a function named "onKeyDown" which is shown here:

        function onKeyDown(e) {
            if (e && e.keyCode == Sys.UI.Key.esc) {
                $(".mpeTarget").each(function() {
                    var mpe = Sys.UI.Behavior.getBehaviorsByType(this, AjaxControlToolkit.ModalPopupBehavior);                    
                    if (mpe.length > 0)
                        mpe[0].hide();
                });
            }
        }


This method checks to see if the pressed key is the 'esc' key. If it is, jQuery is used to iterate through all the DOM objects with css class "mpeTarget". Each of those DOM objects is used in a call to getBehaviorsByType() to get the behavior object and then call its hide() method.

Thursday, February 12, 2009

Getting a reference to a behavior

For my work I use databound repeating controls extensively. Of course, when using a repeating server control, child control ids are mangled by asp.net to prevent naming conflicts. To facilitate client side interaction I have begun using jQuery to get a reference to DOM elements whose names have been mangled by asp.net.

I wanted to be able to dismiss ModalPopupExtenders on my pages by pressing the escape key and the first obvious problem that manifested was that in order to call the hide() method of the ModalPopupExtender behavior, I had to have a reference to it. But because the ModalPopupExtender is a Component and not a DOM object, I cannot use jQuery to select it.

In my next post I'll demonstrate how I ultimately implemented the functionality I desired, but for now I merely wish to describe a couple of ways to get a reference to a behavior.

I used the Sys.UI.Behavior.getBehaviorsByType() method which works like this:

var mpeArray = Sys.UI.Behavior.getBehaviorsByType($get("btnCausePopup"), 
            AjaxControlToolkit.ModalPopupBehavior);


getBehaviorsByType() returns an array of behaviors. The first parameter it takes is the TargetControlID of the Ajax Control Toolkit control, or in other words, the control that is being extended by the behavior. Here's what my ModalPopupExtender looks like:

 <ajaxToolkit:ModalPopupExtender runat="server" ID="mpePopup" TargetControlID="btnCausePopup"
        PopupControlID="pnlPopup" />


The second parameter is the type of the Sys.UI.Behavior objects to find. Be sure to include the AjaxControlToolkit namespace. In this case the type is "AjaxControlToolkit.ModalPopupBehavior," not "AjaxControlToolkit.ModalPopupExtender." If you are not sure what the type is for the control you are using, one way to find it is to view the source of the page from the browser and search for the value of the TargetControlID. You will find something that looks something like this:

Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ModalPopupBehavior, {"PopupControlID":"pnlPopup","id":"mpePopup"},
       null, null, $get("btnCausePopup"));


Here, the bold text indicates the Type. Once you have the array of behaviors, iterate through the array to access each individual behavior, setting properties or calling methods at your leisure.

Another useful method in this regard is Sys.UI.Behavior.getBehaviorByName(). Its first parameter is the same as getBehaviorsByType. The second parameter is the name property of the behavior.

Wednesday, February 11, 2009

$get and $find - documented

Bookmark these links! The $get and $find client side APIs are notoriously difficult to find documentation for because their names do not lend well to googling due to the initial '$'. Here are direct links to their official documentation:

$get

$find

Also worth bookmarking is the main page of the asp.net client side reference

Wednesday, January 21, 2009

My button doesn't work (Validation in a databound repeating control)

If you include validation controls in a templated control (GridView, Repeater, Accordion) make sure to set the ValidationGroup property of the validation controls to something that will resolve to a unique value for each item. If you don't do this, buttons may appear to stop functioning as the validator they are connected with is not the one you are working with in the given row.

An example of setting the ValidationGroup to something unique to each row might be:

ValidationGroup='<%# String.Format("RecordEdit_{0}",Eval("RecordID")) >%'

Thursday, January 15, 2009

<%# Eval() %>

This post describes the difference between these two data binding constructs:

<%# Eval() %>
and
<%# DataBinder.Eval() %>

Friday, January 02, 2009

When I implemented a global.asax file and its Application_Error() event handler, I got a vague "File does not exist" error along with the error number -2147467259. This blog post helped me to discover the missing file. Essentially, add the following line to the watch list and set a break point in Application_Error() to see the missing file:

((HttpApplication)sender).Context.Request.Url