Mike Borozdin's Blog

A blog about programming, web and IT in general

ASP.NET TreeView and Checkboxes

Download the project files.

Although the TreeView control is a very powerful one, there are some pitfalls that you can run into when using it. One of the most popular problem is using checkboxes in TreeView. Despite the fact that you can enable checkboxes by setting ShowCheckBoxes="All" you still have to write a lot of JavaScript code to make it work properly. The problems are:

  • When you check a parent or a branch node the child nodes don't get checked
  • TreeView doesn't generate <label> tags around the nodes text, so when user click on the text next to the checkbox, the latter don't get checked

My solution written with JavaScript and with the help of ASP.NET AJAX library (so, don't forget to put a ScriptManager to your page) solves both problems. You can view the demo here. It was tested in IE, Firefox, Safari and Opera.

There are some other solutions, but they are missing some features, for instance they don't convert a link click to a checkbox selection.

Providing you have a TreeView on your page, you have similar code:

<asp:TreeView ID="someTree" runat="server" ShowCheckBoxes="All">
    <Nodes>
        <asp:TreeNode Text="Root"> 
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Branch">
                <asp:TreeNode Text="Leaf" />
                <asp:TreeNode Text="Leaf" />
            </asp:TreeNode>
        </asp:TreeNode>
        <asp:TreeNode Text="Root">
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Leaf" />
            <asp:TreeNode Text="Leaf" />
        </asp:TreeNode>
    </Nodes>
</asp:TreeView>

You have to put the following code either to the <head> section or to a separate JavaScript file.

<script type="text/javascript">
    var TREEVIEW_ID ="someTree"; //the ID of the TreeView control
    //the constants used by GetNodeIndex()
    var LINK = 0;
    var CHECKBOX = 1;
    
    //this function is executed whenever user clicks on the node text
    function ToggleCheckBox(senderId)
    {
        var nodeIndex = GetNodeIndex(senderId, LINK);
        var checkBoxId = TREEVIEW_ID + "n" + nodeIndex + "CheckBox";
        var checkBox = document.getElementById(checkBoxId);
        checkBox.checked = !checkBox.checked;
        
        ToggleChildCheckBoxes(checkBox);
        ToggleParentCheckBox(checkBox);
    }
    
    //checkbox click event handler
    function checkBox_Click(eventElement)
    {
        ToggleChildCheckBoxes(eventElement.target);
        ToggleParentCheckBox(eventElement.target);
    }
    
    //returns the index of the clicked link or the checkbox
    function GetNodeIndex(elementId, elementType)
    {
         var nodeIndex;
         if(elementType == LINK)
         {
            nodeIndex = elementId.substring((TREEVIEW_ID + "t").length);
         }
         else if (elementType == CHECKBOX)
         {
            nodeIndex = elementId.substring((TREEVIEW_ID + "n").length, elementId.indexOf("CheckBox"));
         }
         return nodeIndex;
    }
    
    //checks or unchecks the nested checkboxes
    function ToggleChildCheckBoxes(checkBox)
    {
        var postfix = "n";
        var childContainerId = TREEVIEW_ID + postfix + GetNodeIndex(checkBox.id, CHECKBOX) + "Nodes";
        var childContainer = document.getElementById(childContainerId);
        if (childContainer)
        {
            var childCheckBoxes = childContainer.getElementsByTagName("input");
            for (var i = 0; i < childCheckBoxes.length; i++)
            {
                childCheckBoxes[i].checked = checkBox.checked;
            }
        }
    }
    
    //unchecks the parent checkboxes if the current one is unchecked
    function ToggleParentCheckBox(checkBox)
    {
        if(checkBox.checked == false)
        {
            var parentContainer = GetParentNodeById(checkBox, TREEVIEW_ID);
            if(parentContainer) 
            {
                var parentCheckBoxId = parentContainer.id.substring(0, parentContainer.id.search("Nodes")) + "CheckBox";
                if($get(parentCheckBoxId) && $get(parentCheckBoxId).type == "checkbox") 
                {
                    $get(parentCheckBoxId).checked = false;
                    ToggleParentCheckBox($get(parentCheckBoxId));
                }
            }
        }
    }
    
    //returns the ID of the parent container if the current checkbox is unchecked
    function GetParentNodeById(element, id)
    {
        var parent = element.parentNode;
        if (parent == null)
        {
            return false;
        }
        if (parent.id.search(id) == -1)
        {
            return GetParentNodeById(parent, id);
        }
        else
        {
            return parent;
        }
    }
</script>

This code should be put to the bottom of the page, that will assign the event handler created above.

<script type="text/javascript">
    var links = document.getElementsByTagName("a");
    for (var i = 0; i < links.length; i++)
    {
        if (links[i].className == TREEVIEW_ID + "_0")
        {
            links[i].href = "javascript:ToggleCheckBox(\"" + links[i].id + "\");";
        }
    }
    
    var checkBoxes = document.getElementsByTagName("input");
    for (var i = 0; i < checkBoxes.length; i++)
    {
        if (checkBoxes[i].type == "checkbox")
        {
            $addHandler(checkBoxes[i], "click", checkBox_Click);
        }
    }
</script>

 

Although we don't use AJAX here, the code still makes uses of ASP.NET AJAX library because it provide convenient event handling classes and quick shortcuts.

The other way to solve the problem is to use CSS Friendly Control Adapters that contain a TreeView modification that has all the features we need, moreover it generates <ul> and <li> instead of tables that dramatically reduces the size of the HTML code.

The project files are located here.


Tags:
Posted by Mike Borozdin on Monday, August 11, 2008 9:34 AM GMT
Shout it Kick it!  
Permalink | Comments (19) | Post RSSRSS comment feed

Paradox of Open Source Software

Everybody knows the benefits of the open source software. Of course, the main benefit is the availability of the source code and the licence that allows us to perform he modifications and to distribute the derived product. The availability of the source code makes us think that the source code should be perfect. However this is not always true.

Don't get me wrong. I really love some open source projects. In addition to great features, they have neat code that can be used as an excellent learning material. Even Microsoft understands that. CodePlex and MSDN Code Gallery are good examples of that fact.

However there are project, very popular projects, I must admit, that simply badly written. I've been developing with PHP for many years and I ran into really popular, but no so well written applications. I don't imply that it's a fault only by PHP applications, I just had a lot of experience with that.

That may sound really ridiculous, because when the source code is open, many people can read it and make better. However sometimes it doesn't happen, people prefer to concentrate on adding cool features that is right, but on the other hand it makes the code unmaintable and bloated.

That is really a paradox. I think it can be explained by the fact that the open source software is built mostly by the enthusiasts who love to do what they like. I mean that is far more interesting to introduce brand new features rather than to worry about the quality of the source code.

I might be wrong on that though. So, I want to hear your opinion. Have only dealt with well written open source projects? If you have an experience with poor written ones, what do you think is the cause of that?


Tags:
Posted by Mike Borozdin on Saturday, August 09, 2008 3:51 PM GMT
Shout it Kick it!  
Permalink | Comments (4) | Post RSSRSS comment feed

Free Reference Cards on Development Topics or Another Reason to Love DZone

I have already said that I love DZone, but I want to say more - I was really happy to discover another lovely feature of DZone - Refcardz. Refcardz are basically reference cards on development topics available to download for free for DZone members, if you are not a DZone member, then you can register for free.

The reference card are in PDF and ready to be printed. They must be kept near your desk because if you need a quick reference, Refcardz will always help you. I believe the Refcardz on jQuery Selectors and Windows PowerShell will be useful for many people.

The current Refcardz are mostly on Java, RIA and AJAX, as well as on the general programming principles.

General programming principles:

Java:

RIA:

Windows PowerShell:

XML:


They are planning to publish more refcardz, you can subscribe to get notified.


Tags:
Posted by Mike Borozdin on Thursday, August 07, 2008 5:58 AM GMT
Shout it Kick it!  
Permalink | Comments (3) | Post RSSRSS comment feed

Is Microsoft Losing Publicity War?

I guess it's not a secret to anyone that many people complain about Vista, they simply hate it, they post negative comments on the web forums and so on. But are those complaints reasonable? These thing started to happen not after the Vista release, but some time before that! So, when Vista finally came out, it already had a negative image and it still has, no matter if it's a really good operating system. For instance, I know some people who are going to buy a laptop but they are scared to death by Vista, that sounds ridiculous and even stupid, but that's a fact. They don't want to have Windows Vista installed on their laptop instead they are trying to find one with XP installed.

It doesn't have anything to do with Vista qualities, only with its image as a faulty, unstable OS, where many applications don't work and your hardware don't work as well. Of course, it's not true, however that is the image of the OS. Although Microsoft says that Windows Vista sales are pretty ok, that image hurts it.

The same thing applies to Internet Explorer. Although IE8 has successfully passed the Acid Test, it is still considered to be a piece of junk, even though Microsoft deliberately issued a beta version for web developers who can play with it, who can tweak their web sites if they are not properly displayed in IE 8.

So, even though Microsoft release really good products, unfortunately they cannot get over their bad image. However, this applies only to the masses, because the IT professionals are usually better aware of the real quality of the Microsoft products.


Tags:
Posted by Mike Borozdin on Tuesday, August 05, 2008 4:39 AM GMT
Shout it Kick it!  
Permalink | Comments (9) | Post RSSRSS comment feed

Useful Live Writer Plug-Ins for Developers

Although Windows Live Writer is a great tool that simplifies the life of every blogger it doesn't has all the needed features by default. Hopefully there are many plug-ins that can be found in the gallery. If you write about programming that you must be particularly interested in the plug-ins that allows you to paste formatted code snipped, links to MSDN and some other things. I decided to make a categorized list of such plug-ins.

Code:

Console output:

This plug-in allows you to paste a formatted text from the console output, either from Windows PowerShell or from cmd.

Links:

  • MSDN Locator - this plug-in allows you to simply paste a link to a MSDN article by specifying a class or a method name
  • XFN Link Editor - this plug-in allows you specify the XFN attributes of links

There must be some other very useful plug-in, so feel free to share some links :-).


Posted by Mike Borozdin on Monday, August 04, 2008 9:54 AM GMT
Shout it Kick it!  
Permalink | Comments (0) | Post RSSRSS comment feed

Adding Client-Side Events To Extender Controls

In the previous articles I wrote about how to use the extender controls available in ASP.NET AJAX Control Toolkit, how to process them on the server, validate the data. It mainly dealt with a server-side part. Today, I'll show you how to add client-side events to the extender controls.

As an example, I'll use Slider again. But unlike the previous article where we processed its value on the server, we'll use its value directly on the page with JavaScript to resize an image, although it won't actually resize the picture, but it will just resize it on the page, i.e., just change the width and height attributes of the <img /> tag.

Let's start, add some image, two sliders for width and height, two text boxes and two labels.

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
    Width:<br />
    <asp:TextBox ID="txtWidth" runat="server" />
    <asp:Label ID="lblWidth" runat="server" />
    <cc1:SliderExtender ID="sliderWidth" runat="server" TargetControlID="txtWidth"
        BoundControlID="lblWidth" Minimum="100" Maximum="800">
    </cc1:SliderExtender>
    <br />
    
    Height:<br />
    <asp:TextBox ID="txtHeight" runat="server" />
    <asp:Label ID="lblHeight" runat="serBver" />
    <cc1:SliderExtender ID="sliderHeight" runat="server" TargetControlID="txtHeight"
        BoundControlID="lblHeight" Minimum="100" Maximum="532">
    </cc1:SliderExtender>
    <br />
    
    <asp:Image ID="imgPanda" runat="server" ImageUrl="http://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Giant_Panda_2004-03-2.jpg/800px-Giant_Panda_2004-03-2.jpg" />
</div>
</form>

 

We want to resize the image on moving the slider, since when we move the slider handler, the actual value is being changed in the assigned TextBox, we should add the onChanged even to the TextBox.

 

protected void Page_Load(object sender, EventArgs e)
{
    txtWidth.Attributes.Add("onChange", "ChangeWidth()");
    txtHeight.Attributes.Add("onChange", "ChangeHeight()");
}

 

And add the JavaScript event handlers.

<script type="text/javascript">
    function ChangeWidth()
    {
        $get("imgPanda").width = $get("txtWidth").value;
    }
    
    function ChangeHeight()
    {
        $get("imgPanda").height = $get("txtHeight").value;
    }
</script>

By the way, please note the we are using the $get() shortcut here that actually invokes document.getElementById()

Let's test it. The image gets really resized but only when you release the button, but not when you just move the slider handle. This can be corrected by setting the RaiseChangeOnlyOnMouseUp to false.

<cc1:SliderExtender ID="sliderWidth" runat="server" TargetControlID="txtWidth"
    BoundControlID="lblWidth" Minimum="100" Maximum="800" RaiseChangeOnlyOnMouseUp="false">
</cc1:SliderExtender>
<%-- some other tags--%>
<cc1:SliderExtender ID="sliderHeight" runat="server" TargetControlID="txtHeight"
    BoundControlID="lblHeight" Minimum="100" Maximum="532" RaiseChangeOnlyOnMouseUp="false">
</cc1:SliderExtender>

Conclusion

In this tutorial we have learnt how to add how add client-side events to the extender controls, how to handle them. We have also learnt about the RaiseChangeOnlyOnMouseUp attribute of the Slider control.

Your feedback is appretiated.


Tags: ,
Posted by Mike Borozdin on Sunday, August 03, 2008 10:15 AM GMT
Shout it Kick it!  
Permalink | Comments (3) | Post RSSRSS comment feed

ASP.NET AJAX Control Toolkit Extenders: Processing And Validation

In the previous article I told you about ASP.NET AJAX Control Toolkit, I explained its purpose, listed the available controls and showed an example. However, I think some things should be explained a bit more detailed. Today I'm going to talk about the extenders controls, I already mentioned them in the previous article, but I can repeat. An extender is a control that is a based on an ASP.NET web control, but provides an additional client-side functionality. Slider is a good example of an extender, in looks like a real sliding control in desktop applications, but in fact it just extends the TextBox control.

So, its value is actually stored in the TextBox that is linked with a slider by the TargetControlID attribute, thus when processing a form you should look for the value in the TextBox. Example:

Default.aspx

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
    <%--The TextBox that contains the Slider value--%>
    <asp:TextBox ID="txtSlider" runat="server" />
    <asp:Label ID="lblSlider" runat="server" />

    <cc1:SliderExtender ID="SliderExtender1" runat="server" TargetControlID="txtSlider"
        BoundControlID="lblSlider">
    </cc1:SliderExtender>
    
    <asp:Button ID="btnSubmit" runat="server" Text="Submit" onclick="btnSubmit_Click" />
</div>
</form>

Default.aspx.cs

protected void btnSubmit_Click(object sender, EventArgs e)
{
    //We use TextBoxID.Text to access the Slider value
    Response.Write(txtSlider.Text); 
}

The next important thing is validation. Although many extenders do help users to enter a correct value (i.e. sliders, numeric up-downs, mask edits, filtered text boxes), we still need to validate the input, because if JavaScript, for some reasons, is disabled, users will see a plain text box instead of a fancy control or some people can try to hack your application by putting incorrect values into the form. So, there is nothing difficult, just use plain ASP.NET validators. If the value should fall between 1 and 10, then add a RangeValidator and RequiredValidator to check if the value isn't empty.

<%--The TextBox that contains the Slider value--%>
<asp:TextBox ID="txtSlider" runat="server" />
<asp:Label ID="lblSlider" runat="server" />

<%-- The validator that checks the value range --%>
<asp:RequiredFieldValidator runat="server" ControlToValidate="txtSlider" 
    ErrorMessage="The value shouldn't be empty" Display="Dynamic" />
<%-- The validator that checks whether the value is empty --%>
<asp:RangeValidator runat="server" ControlToValidate="txtSlider" Type="Integer" 
    MinimumValue="1" MaximumValue="10" ErrorMessage="The value should be between 1 and 10" 
    Display="Dynamic" />

<cc1:SliderExtender ID="SliderExtender1" runat="server" TargetControlID="txtSlider"
    BoundControlID="lblSlider" Minimum="1" Maximum="10">
</cc1:SliderExtender>

Tags: ,
Posted by Mike Borozdin on Friday, August 01, 2008 10:09 AM GMT
Shout it Kick it!  
Permalink | Comments (1) | Post RSSRSS comment feed