Update: The javascript on this blog has been suppressed by the blogger's parser. Consequently, some of the functionality may not work. Inconvenience is regretted.

Tuesday, May 20, 2008

Leveraging Asp.Net WebForms Client Library

While working with asp.net, most front end developers have their own javascript library of utility functions like: (get/set)ElementPosition, getScroll(x/y), (add/remove)ClassName, etc. Most of the times there is no need to have your own versions of some of the more commonly used javascript utility functions as they are already present in the Webresourse.axd javascript file embedded (not always) in your asp.net page and most importantly the functions are Cross Browser. The utility functions in this client library are used for asp.net callbacks & postbacks, in addition to being used by asp.net validation, treeview and menu client libraries (yes, there are seperate libraries for all these). So, if you are using asp.net menu/treeview/validators/callbacks on your webpage, you can happily use the functions provided in the library. If you want to ensure that the library is included in your page, just use a nice to have feature in your asp.net page: MaintainScrollPositionOnPostback:

<%@ Page Language="C#" MaintainScrollPositionOnPostBack="true"
AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>



or appliying it sitewide thru web.config as:



<system.web>
<pages maintainScrollPositionOnPostBack="true">
</pages>
</system.web>




The script file is included in my page as:

<script type="text/javascript" src="/TestWebsite/WebResource.axd?d=X2ApKqYNd-qiVRFcah5bwg2&t=633437918395970000">

Following is the list of some of the commonly used functions present in the library:



    WebForm_GetScrollX

    WebForm_GetScrollY

    WebForm_SetElementX

    WebForm_SetElementY

    WebForm_GetElementById

    WebForm_SetElementWidth

    WebForm_SetElementHeight

    WebForm_RemoveClassName

    WebForm_AppendToClassName

    WebForm_GetParentByTagName

    WebForm_GetElementByTagName

    WebForm_GetElementsByTagName

    WebForm_GetElementPosition - another version here


Hope this is helpful.


pushp

Thursday, April 3, 2008

AJAX Issues With WATIN

Recently I started Automating the UI test cases for our AJAX enabled asp.net website with Watin. It was not long before that I ran into problems as Watin seems to be ajax postback agnostic.

I had a master dropdownlist and a child dropdownlist (this is disabled until something is selected in the master DDL). On the selection of an item in the master DDL there is an AJAX postback that would get data for the child DDL and then an item in the child DDL will be selected. To automate this scenario I wrote the following code:


IE ie = new IE("http://localhost/MySite/Default.aspx");
SelectList ddlMaster = ie.SelectList("ddlMaster");
ddlMaster.Option(Find.ByValue("India")).Select();
SelectList ddlChild = ie.SelectList("ddlChild");
ddlChild.Option(Find.ByValue("Hyderabad")).Select();


The Problem: The first DDL gets selected perfectly and an asynchronous postback happens but I get an exception in the fourth line (which tries to select a value in the child DDL) saying "Could not find OPTION element tag matching criteria: Attribute 'value' with value 'Hyderabad'". The problem was clear - we are trying to select a value in the child DDL before asynchronous postback is over and the child DDL is populated with values.

The first thought that came into my mind was to put a Thread.Sleep but I know it wasn't a good solution since I can never be sure of how long the asynchronous request is going to take. So I looked for something that could tell me if page is still in async postback. Enter the PageRequestManager Class in asp.net ajax. It has a property called isInAsyncPostback that you can query to know if page is in async postback. So all I needed was a way to run some javascript from the code and thank god, there is Eval in Watin to do just that.

The Solution:

1)Updated the aspx page with a method to return true if page is in async postback as below:


<asp:ScriptManager ID="scrptMgr" runat="server" EnablePartialRendering="true">
</asp:ScriptManager> <!-- script manager shown for completeness -->
<script type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
function IsPageInAsyncPostback(){
return prm.get_isInAsyncPostBack();
}

</script>

2) Wrote a helper method to wait until async postback is complete:


private static void WaitForAsyncPostBackToComplete(IE ie)
{
bool isInPostback = true;
while (isInPostback)
{
isInPostback = Convert.ToBoolean(ie.Eval("IsPageInAsyncPostback();"));
if (isInPostback)
Thread.Sleep(200); //sleep for 200ms and query again
}
}


3) Updated the test scenario code as below:


IE ie = new IE("http://localhost/MySite/Default.aspx");
SelectList ddlMaster = ie.SelectList("ddlMaster");
ddlMaster.Option(Find.ByValue("India")).Select();
WaitForAsyncPostBackToComplete(ie);
SelectList ddlChild = ie.SelectList("ddlChild");
ddlChild.Option(Find.ByValue("Hyderabad")).Select();


and all was merry!!!

Hope this is helpful.

pushp

Wednesday, June 20, 2007

Asp.Net Viewstate

One of the most neglected(in terms of understanding) and most used(functionally) features of asp.net is Viewstate. Most of the times the asp.net developer keep using viewstate feature as a black box. I don't need to tell you Viewstate can make hell lot of difference in the performance of your site, IF USED PROPERLY.

The other day i stumbled upon an excellent article that tells you everything you need to know about asp.net viewstate. Check it out here: http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx

Happy Programming!!

Monday, June 18, 2007

Firefox is the way to go..

This one is for those of you who have not yet embraced Mozilla's great browser Firefox. Firefox is designed for speed and security and i can assure you once you experience the power of fifefox you'll not try anything else again. Yes, the new browser from Microsoft, Internet Explorer 7 has introduced some good features besides mimicking Firefox's Tabbed Interface but it still sucks when it comes to speed. IE7 gets frustratingly slow if you open up a few tabs. Besides, firefox is having some great add-ons(out of hundreads of add-ons available) that you would just love.

Few of my favourite ones are:

Cooliris Previews: This add-on allows you to preview links on a page, i.e., you can view pages pointed to by the links on the current page, directly inside your current page without leaving it.

IE Tab: Some sites render properly only in IE and they would have imperfect appearance in Firefox(experienced this with MSDN site a lot). Now we have a great add-on called IE Tab that embeds IE directly in the firefox tabs. In effect IE's rendering engine is used for the sites you would like to open in IE.

FoxMarks: Take your bookmarks wherever you go.

Finally, if you are a web developer Firebug is indispensable.

Folks, don't miss this great experience anymore...go and install Firefox.

Wednesday, June 13, 2007

Getting TreeNode values on the client side with javascript

In a lot of scenarios it is required to get the value of a treenode (for an asp.net treeview) on the client side. While on the server we can get the nodevalue directly via the Value property of the Treenode, on the client it seems there is no corresponding attribute added to the anchor(yes treenodes are rendered as anchor(<a ..) tags) . Getting the node's text is easy - just get hold of the node's anchor element and call its innerText property. But how do we get the node value? Well, if you look closely at the rendered HTML for the node, you could find the node value lurking inside the href property for the node (provided you have not set the NavigateUrl property for the node manually). Something like:

href="javascript:__doPostBack('TreeView1','sRoot\\firstChild\\...\\nodeValue')"

All we have to do is to extract that bold part from what the href property gives us.
Whereas there may be various scenarios where we may need the node value on the client, I would demonstrate it for the case of the node click itself - you want the value when you click a treenode. Try this:

1) Add an attribute to the treeview in code behind as:

if (!IsPostBack)
{
TreeView1.Attributes.Add("onclick", "return OnTreeClick(event)");
}


2) Put the below code in head tag of .aspx page
Oops!! The current security settings of your browser do not allow the scripts on the page to access your clipboard.If you are using mozilla firefox, you may change security settings as follows: Type about:config in the url bar and set signed.applets.codebase_principal_support to true. For more info, check here or contact blog author.
 

<script type="text/javascript">
function OnTreeClick(evt) {
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var nodeClick = src.tagName.toLowerCase() == "a";
if (nodeClick) {
//innerText works in IE but fails in Firefox (I'm sick of browser anomalies), so use innerHTML as well
var nodeText = src.innerText || src.innerHTML;
var nodeValue = GetNodeValue(src);
alert("Text: " + nodeText + "," + "Value: " + nodeValue);
}
return false; //comment this if you want postback on node click
}
function GetNodeValue(node) {
var nodeValue = "";
var nodePath = node.href.substring(node.href.indexOf(",") + 2, node.href.length - 2);
var nodeValues = nodePath.split("\\");
if (nodeValues.length > 1)
nodeValue = nodeValues[nodeValues.length - 1];
else
nodeValue = nodeValues[0].substr(1);

return nodeValue;
}
</script>


Another scenario i can think of is when you have check boxes in the treeview and you want to get the nodevalue for the node that is checked. In that case you can just get the checkbox ( rendered as input tag) element first and then you can get the reference to the node via the nextSibling property for the checkbox.

That's all. Hope this is helpful.

pushp

Tuesday, June 5, 2007

Client Side Expand/Collapse All Nodes For ASP.NET 2.0 Treeview

The asp.net treeview provides a lot of features/functions, one of which is the Expand/Collapse All functionality on the server side. The Treeview Control has got two function for doing just that: TreeView.ExpandAll(), TreeView.CollapseAll(). But going to server to accomplish such a simple functionality seemed a little odd to me. So, I wrote some javascript that does precisely the same. Checkout the below script:

Oops!! The current security settings of your browser do not allow the scripts on the page to access your clipboard.If you are using mozilla firefox, you may change security settings as follows: Type about:config in the url bar and set signed.applets.codebase_principal_support to true. For more info, check here or contact blog author.
 

function TreeviewExpandCollapseAll(treeViewId, expandAll)
{
var displayState = (expandAll == true ? "none" : "block");
var treeView = document.getElementById(treeViewId);
if(treeView)
{
var treeLinks = treeView.getElementsByTagName("a");
var nodeCount = treeLinks.length;

for(i=0;i<nodeCount;i++)
{
if(treeLinks[i].firstChild.tagName)
{
if(treeLinks[i].firstChild.tagName.toLowerCase() == "img")
{
var currentToggleLink = treeLinks[i];
var childContainer = GetParentByTagName("table", currentToggleLink).nextSibling;
if (childContainer.style.display == displayState)
{
eval(currentToggleLink.href);
}
}
}
}//for loop ends
}
}

//utility function to get the container of an element by tagname
function GetParentByTagName(parentTagName, childElementObj)
{
var parent = childElementObj.parentNode;
while(parent.tagName.toLowerCase() != parentTagName.toLowerCase())
{
parent = parent.parentNode;
}
return parent;
}


Get Formatted Version Of Above Script

The above script could be used as:


<a href="javascript:TreeviewExpandCollapseAll('<%=TreeView1.ClientID%>', true)">Expand All</a>
<a href="javascript:TreeviewExpandCollapseAll('<%=TreeView1.ClientID%>', false)">Collapse All</a>
<asp:TreeView ID="TreeView1" .....................



That's all there is to it.

Hope this is helpful.

Update (7/25/2008): The script has been updated to fix a minor bug concerning the toggle images being screwed up if ShowLines is false for the treeview. Thanks to comments from users, particularly to ryan.

pushp

Monday, June 4, 2007

Asp.Net 2.0 Treeview Checkbox Check/Uncheck All script

Its a very common requirement to have the parent-child check behaviour in asp.net treeview. To define the problem:
1)Check all the child nodes if the parent is checked and uncheck all child nodes if parent is unchecked ( well, this part is simple).
2)If a node at any level is checked and all its siblings are already checked then the parent node should be checked and the same should apply for the parent node(i.e., if its siblings are checked....), this should happen till the root node.
3)If a node at any level is unchecked then the parents( ma, grand ma, grand grand ma....) up to the root node must be unchecked.

Well there have been scripts on the net that only half accomplished the task(check footnotes). So I wrote the script that solves the problem completely, upto best of my knowledge. I've tested in IE 7 and Firefox 2.0, hope it works fine for you all.

Here's how to implement it:

Step 1: In the page load in code behind file add an attribute to the treeview as:

If(!isPostBack)
{
    TreeView1.Attributes.Add("onclick","OnTreeClick(event)");
}

The desired affect could also be accomplished by direclty adding the attribute to the treeview tag in .aspx file as: <asp:treeview onclick="OnTreeClick(event)"... which would cause Visual Studio to display a warning but it works anyway but the codebehind way of doint it is the right way.

Step 2: Put the below script in the head section of your .aspx page:

Oops!! The current security settings of your browser do not allow the scripts on the page to access your clipboard.If you are using mozilla firefox, you may change security settings as follows: Type about:config in the url bar and set signed.applets.codebase_principal_support to true. For more info, check here or contact blog author.
 

function OnTreeClick(evt)
{
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var isChkBoxClick = (src.tagName.toLowerCase() == "input" && src.type == "checkbox");
if(isChkBoxClick)
{
var parentTable = GetParentByTagName("table", src);
var nxtSibling = parentTable.nextSibling;
//check if nxt sibling is not null & is an element node
if(nxtSibling && nxtSibling.nodeType == 1)
{
if(nxtSibling.tagName.toLowerCase() == "div") //if node has children
{
//check or uncheck children at all levels
CheckUncheckChildren(parentTable.nextSibling, src.checked);
}
}
//check or uncheck parents at all levels
CheckUncheckParents(src, src.checked);
}
}

function CheckUncheckChildren(childContainer, check)
{
var childChkBoxes = childContainer.getElementsByTagName("input");
var childChkBoxCount = childChkBoxes.length;
for(var i=0;i<childChkBoxCount;i++)
{
childChkBoxes[i].checked = check;
}
}

function CheckUncheckParents(srcChild, check)
{
var parentDiv = GetParentByTagName("div", srcChild);
var parentNodeTable = parentDiv.previousSibling;
if(parentNodeTable)
{
var checkUncheckSwitch;
if(check) //checkbox checked
{
var isAllSiblingsChecked = AreAllSiblingsChecked(srcChild);
if(isAllSiblingsChecked)
checkUncheckSwitch = true;
else
return; //do not need to check parent if any(one or more) child not checked
}
else //checkbox unchecked
{
checkUncheckSwitch = false;
}

var inpElemsInParentTable = parentNodeTable.getElementsByTagName("input");
if(inpElemsInParentTable.length > 0)
{
var parentNodeChkBox = inpElemsInParentTable[0];
parentNodeChkBox.checked = checkUncheckSwitch;
//do the same recursively
CheckUncheckParents(parentNodeChkBox, checkUncheckSwitch);
}
}
}

function AreAllSiblingsChecked(chkBox)
{
var parentDiv = GetParentByTagName("div", chkBox);
var childCount = parentDiv.childNodes.length;
for(var i=0;i<childCount;i++)
{
if(parentDiv.childNodes[i].nodeType == 1)
{
//check if the child node is an element node
if(parentDiv.childNodes[i].tagName.toLowerCase() == "table")
{
var prevChkBox = parentDiv.childNodes[i].getElementsByTagName("input")[0];
//if any of sibling nodes are not checked, return false
if(!prevChkBox.checked)
{
return false;
}
}
}
}
return true;
}

//utility function to get the container of an element by tagname
function GetParentByTagName(parentTagName, childElementObj)
{
var parent = childElementObj.parentNode;
while(parent.tagName.toLowerCase() != parentTagName.toLowerCase())
{
parent = parent.parentNode;
}
return parent;
}


Get Formatted Version Of Above Script

The script is pretty much self explanatory with function names saying it all.
Comments awaited.

Footnotes: There has been other scripts on the net that accomplished the job only partially, the one that reached closest to doing it could be found in this thread on asp.net forums, i too have posted my solution there.