Free 3 Months
|
| |
| brendan_Rice | Asp.Net User |
| Treeview OnNodeExpanded - OnNodeCollapsed | 11/5/2006 12:50:23 PM |
0/0 | |
|
First of great job with the control adapters this is functionality that I thought would have been implemented for .Net 2 and was dissapointed when I found outit was absent, thanks.
I have implemented the control adapters on a site I am building and have a treeview as navigation. The treeview maintains state on postback but obviously not when I navigate away from the page as I am rebinding. The treeview is held in a master page.
I want to maintain the state of the treeview across my site and I after research think the best way would be though a session that stores the treeview expanded node state. I realise this would mean that when a node is expanded or collapsed the site would need to postback so I can collect the node and its changed state. I do not want to postback just for this so I was going to implement the treeview in an Ajax UpdatePanel.
When I apply Beta 3 control adapters the OnNodeExpanded/OnNodeCollapsed events are not getting fired so I cannot mark the node as expanded in the session.
This would seem like a common problem and for all I have searched I cannot find an answer any help with this would be appreciated. |
| Russ Helfand | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 11/5/2006 5:41:22 PM |
0/0 | |
|
Hi Brendan,
First, you might want to take a look at this posting, http://forums.asp.net/thread/1452175.aspx. It discusses cross page (non-postback) state retention strategies for the adapted TreeView. Basically, my suggestion is that you consider NOT fully retaining the expansion state of the tree but rather use a scheme where the tree simply expands to show the current page. My guess is that many users will simply want to know where they are in the tree; they won't mind if the unrelated portions of the tree collapse when they switch pages. But you, of course, may hold a legitimate alternate opinion (that full and exact tree re-expansion in cross page navigation is really what you need).
Another way to approach this is to leverage the existing JavaScript used by the adapted TreeView to map out its expansion state just prior to posting back. Insidea of using that map information for setting a hidden <input> used by the postback logic to re-expand the tree, you could send that map along to the next page on a query string or in a cookie. Take a look at what is happening in TreeViewAdapter.cs (or vb) in the SaveAdapterViewState method. See the call to register a script that will fire when the form is submitted? It sounds like you will want something very similar when the page navigates to another page. Rather than the script setting up a hidden <input> you would want it to set up a cookie or a query string value just before the page navigates to another page (on the client). You probably can rig that up using some pretty simple JavaScript mods for the client... though I don't have the exact details laid out in my head at the moment.
OK, putting that aside...
You had a question about handling some events like node expand/collapse. If you do decide to use postback and do want to handle these events, you may need to add them to the existing logic found in TreeViewAdapter.cs (or vb) in the RaisePostBackEvent method. Notice how it uses Extender.RaiseAdaptedEvent to call an event handler for other events. You may want to do something similar when the postback handler figures out that it needs to change the expansion state of a node. Remember, though, that there are some special cavaets when using Extender.RaiseAdaptedEvent. It will end up calling public (PUBLIC, PUBLIC, PUBLIC) methods of the page ONLY. DO NOT MAKE YOUR EVENT DELEGATE PROTECTED OR PRIVATE. Also, you will need to use a new attribute for the adapted TreeView, OnAdaptedTreeNodeExpanded (not the usual OnTreeNodeExpanded). There are notes about this whole thing at http://www.asp.net/cssadapters/whitepaper.aspx#SamplesTreeViewNotes.
I know this isn't a complete step-by-step solution. I just wanted to share some thougths with you, hoping that they will help point you in right direction. Good luck with your investigations. Please consider posting notes about your solution when you work it out. Others in the community will benefit from your experience.
Best regards,
Russ Helfand Groovybits.com |
| brendan_Rice | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 11/5/2006 8:07:29 PM |
0/0 | |
|
Thanks for the reply. I was half way through implementing the cookie approach you described above when I recieved your reply.
I thought the cookie apporach would probably be the best as I have read a fair bit of bad press about the session object. Having looked at the code I thought the best place to have my cookie code was TreeviewAdapter.js ExpandCollapse__AspNetTreeView as this seems to be where things actually get expanded or collapsed. What I basically done was write a cookie that represented the expanded node (if a node is collapsed it would be removed from the cookie) as I thought this would be the most efficient approach. The cookie is called "ExpandedTreeNodes" and the node text is split by a '|' i.e. ExpandedNode1 Text|Node2|Node3.
The cookie can then be read from the server and expand the nodes that are represented in it. The modified TreeviewAdapter.js can be found below:
http://www.rafb.net/paste/results/h21vkl62.html |
| Russ Helfand | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 11/5/2006 9:43:26 PM |
0/0 | |
|
Nice. Consider publishing the server-side code, too. I think others would like to see it also.
Yes, using the Session object can be problematic in some situations. The one that most people mention most frequently is when the web app is deployed to a server far. In that situation, you have to implement a special Session store provider that uses a database to persist the session info. Otherwise, Session state is unreliable because resquest/response cycles may end up being handled by different web servers even for the same session (though the server farm can usually be set up to avoid this). In any case, I generally use cookies in lieu of the Session object because cookies are pretty easy to create and use (both client- and server-side). And, because cookies avoid all the problems generally associated with Session.
Anyway... I like your innovation on this problem. I think others will too!
Russ Helfand Groovybits.com |
| brendan_rice | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 11/10/2006 11:59:51 PM |
0/0 | |
|
//YOUR WISH IS MY COMMAND var collapseClass = "AspNet-TreeView-Collapse";
var expandClass = "AspNet-TreeView-Expand";
var showClass = "AspNet-TreeView-Show";
var hideClass = "AspNet-TreeView-Hide";
function IsExpanded__AspNetTreeView(element)
{
return (HasClass__CssFriendlyAdapters(element, collapseClass));
}
function TogglePlusMinus__AspNetTreeView(element, showPlus)
{
if (HasAnyClass__CssFriendlyAdapters(element))
{
var showPlusLocal = IsExpanded__AspNetTreeView(element);
if ((typeof(showPlus) != "undefined") && (showPlus != null))
{
showPlusLocal = showPlus;
}
var oldClass = showPlusLocal ? collapseClass : expandClass;
var newClass = showPlusLocal ? expandClass : collapseClass;
SwapClass__CssFriendlyAdapters(element, oldClass, newClass);
}
}
function ToggleChildrenDisplay__AspNetTreeView(element, collapse)
{
if ((element != null) && (element.parentNode != null) && (element.parentNode.getElementsByTagName != null))
{
var childrenToHide = element.parentNode.getElementsByTagName("ul");
var oldClass = collapse ? showClass : hideClass;
var newClass = collapse ? hideClass : showClass;
for (var i=0; iif ((childrenToHide[i].parentNode != null) && (childrenToHide[i].parentNode == element.parentNode))
{
SwapOrAddClass__CssFriendlyAdapters(childrenToHide[i], oldClass, newClass);
}
}
}
}
function ExpandCollapse__AspNetTreeView(sourceElement)
{
if (HasAnyClass__CssFriendlyAdapters(sourceElement))
{
var expanded = IsExpanded__AspNetTreeView(sourceElement);
TogglePlusMinus__AspNetTreeView(sourceElement, expanded);
ToggleChildrenDisplay__AspNetTreeView(sourceElement, expanded);
//The function below has been added
SetExpandedNodesCookie(sourceElement, expanded);
}
}
//*********************************************************************************************************
//Additional code
//*********************************************************************************************************
function SetExpandedNodesCookie(sourceElement, expanded)
{
var parentCategory = sourceElement.nextSibling.nextSibling.innerText;
var expandedNodesString = ReadCookie("ExpandedTreeNodes");
var expandedNodesArray = new Array();
if(expandedNodesString != null)
{
if(expandedNodesString.indexOf('|') > 0)
{
expandedNodesArray = expandedNodesString.split('|');
}
else if(expandedNodesString.length > 0)
{
expandedNodesArray[0] = expandedNodesString;
}
}
CreateExpandedNodesCookie(expanded, expandedNodesArray, parentCategory)
}
function CreateExpandedNodesCookie(expanded, expandedNodesArray, parentCategory)
{
if(!expanded && !ArrayContains(expandedNodesArray, parentCategory))
{
expandedNodesArray[expandedNodesArray.length] = parentCategory;
CreateCookie("ExpandedTreeNodes", expandedNodesArray.join("|"), 10);
}
else if(expanded && ArrayContains(expandedNodesArray, parentCategory))
{
var newExpandedNodesArray = new Array();
var node;
for (index = 0; index < expandedNodesArray.length; index++)
{
if(RightTrim(parentCategory) != expandedNodesArray[index])
{
newExpandedNodesArray[newExpandedNodesArray.length] = expandedNodesArray[index];
}
}
CreateCookie("ExpandedTreeNodes", newExpandedNodesArray.join("|"), 10);
}
}
function ArrayContains(expandedNodesArray, elementToLookFor)
{
var elementIndexInArrayString = expandedNodesArray.toString().indexOf(RightTrim(elementToLookFor));
if(elementIndexInArrayString >= 0)
{
return true;
}
else
{
return false;
}
}
function CreateCookie(name,value,days)
{
if (days)
{
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
function ReadCookie(name)
{
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++)
{
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function EraseCookie(name) {
createCookie(name,"",-1);
}
function RightTrim(sString)
{
while (sString.substring(sString.length-1, sString.length) == ' ')
{
sString = sString.substring(0,sString.length-1);
}
return sString;
}
//*********************************************************************************************************
function GetViewState__AspNetTreeView(id)
{
var retStr = "";
if ((typeof(id) != "undefined") && (id != null) && (document.getElementById(id) != null))
{
var topUL = document.getElementById(id);
retStr = ComposeViewState__AspNetTreeView(topUL, "");
}
return retStr;
}
function ComposeViewState__AspNetTreeView(element, state)
{
var child = element.firstChild;
var bConsiderChildren = true;
// The following line must be changed if you alter the TreeView adapters generation of
// markup such that the first child within the LI no longer is the expand/collapse <span>.
if ((element.tagName == "LI") && (child != null))
{
var expandCollapseSPAN = null;
var currentChild = child;
while (currentChild != null)
{
if ((currentChild.tagName == "SPAN") &&
(currentChild.className != null) &&
((currentChild.className.indexOf(collapseClass) > -1) ||
(currentChild.className.indexOf(expandClass) > -1)))
{
expandCollapseSPAN = currentChild;
break;
}
currentChild = currentChild.nextSibling;
}
if (expandCollapseSPAN != null)
{
if (expandCollapseSPAN.className.indexOf(collapseClass) > -1)
{
// If the "collapse" class is currently being used then the "collapse" icon is, presumably, being shown.
// In other words, the node itself is actually expanded at the present moment (which is why you now
// have the option of collapsing it. So we mark it as an "expanded" node for purposes of the view state
// we are now accumulating.
state += "e";
}
else if (expandCollapseSPAN.className.indexOf(expandClass) > -1)
{
// This part of the tree is collapsed so we don't need to consider its children.
bConsiderChildren = false;
state += "n";
}
}
}
if (bConsiderChildren && (child != null))
{
state = ComposeViewState__AspNetTreeView(child, state);
}
if (element.nextSibling != null)
{
state = ComposeViewState__AspNetTreeView(element.nextSibling, state);
}
return state;
}
|
| brendan_rice | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 11/11/2006 12:21:47 AM |
0/0 | |
|
//Server side code stick this in the Page Load if (Request.Cookies["ExpandedTreeNodes"] != null)
{
string expandedNodesCookieString = Request.Cookies["ExpandedTreeNodes"].Value.ToString();
string[] previouslyExpandedNodes = expandedNodesCookieString.Split('|');
foreach (TreeNode node in this.TreeView1.Nodes)
{
foreach (string previouslyExpandedNode in previouslyExpandedNodes)
{
if (previouslyExpandedNode.Trim() == node.Text.Trim())
{
node.Expand();
}
}
}
}
|
| BladeTech | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 8/7/2007 8:38:41 PM |
0/0 | |
|
I'm having a lot of problems with this script, mainly with IE. Since TreeView isn't officially supported with an update panel and fires extra events server side, this is the only thing I've found that will let me keep my TreeView in my master page while not showing a post back.
I've only tested it on IE6. It gives me a JS error at line 35 (it was 37 at some point) char 10 Error: Expected ';'. It seems to work without complaint in FireFox though. I think I've found some things, but I don't know JS that well and one thing I changed gives me an error. OK, the following starts at line 26 function ToggleChildrenDisplay__AspNetTreeView(element, collapse) { if ((element != null) && (element.parentNode != null) && (element.parentNode.getElementsByTagName != null)) { var childrenToHide = element.parentNode.getElementsByTagName("ul"); var oldClass = collapse ? showClass : hideClass; var newClass = collapse ? hideClass : showClass; for (var i=0; iif ((childrenToHide[i].parentNode != null) && (childrenToHide[i].parentNode == element.parentNode)) { SwapOrAddClass__CssFriendlyAdapters(childrenToHide[i], oldClass, newClass); } } } } I seems to me that the closing brace at line 37 is completely extra first of all. Also, it seems to me that line 33 is missing a closing parentheses. Also in line 33, I THOUGHT iif should have been just if, but that throws a syntax error when I change that. Of coarse, none of this involves a missing ;. The only one of those that I THINK I found was at line 72: 70 } 71 } 72 CreateExpandedNodesCookie(expanded, expandedNodesArray, parentCategory) }
function CreateExpandedNodesCookie(expanded, expandedNodesArray, parentCategory) But obviously I'm missing something, or I wouldn't be here. Are my assumptions correct, and what about the iif thing? What am I missing here? Thanks! |
| BladeTech | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 8/7/2007 10:28:56 PM |
0/0 | |
|
I figured it out. At least it seems to work. I looked at some JS for loops and found they're much like c# so I knew there was a big chunk missing. Fortunately the missing code was in the original JS file. That, and the ; at line 72 was missing. Here's the corrected code if anyone is interested. I have to run now, but I'll do a more thorough testing later. var collapseClass = "AspNet-TreeView-Collapse"; var expandClass = "AspNet-TreeView-Expand"; var showClass = "AspNet-TreeView-Show"; var hideClass = "AspNet-TreeView-Hide";
function IsExpanded__AspNetTreeView(element) { return (HasClass__CssFriendlyAdapters(element, collapseClass)); }
function TogglePlusMinus__AspNetTreeView(element, showPlus) { if (HasAnyClass__CssFriendlyAdapters(element)) { var showPlusLocal = IsExpanded__AspNetTreeView(element); if ((typeof(showPlus) != "undefined") && (showPlus != null)) { showPlusLocal = showPlus; } var oldClass = showPlusLocal ? collapseClass : expandClass; var newClass = showPlusLocal ? expandClass : collapseClass; SwapClass__CssFriendlyAdapters(element, oldClass, newClass); } }
function ToggleChildrenDisplay__AspNetTreeView(element, collapse) { if ((element != null) && (element.parentNode != null) && (element.parentNode.getElementsByTagName != null)) { var childrenToHide = element.parentNode.getElementsByTagName("ul"); var oldClass = collapse ? showClass : hideClass; var newClass = collapse ? hideClass : showClass; for (var i=0; i<childrenToHide.length; i++) { if ((childrenToHide[i].parentNode != null) && (childrenToHide[i].parentNode == element.parentNode)) { SwapOrAddClass__CssFriendlyAdapters(childrenToHide[i], oldClass, newClass); } } } } function ExpandCollapse__AspNetTreeView(sourceElement) { if (HasAnyClass__CssFriendlyAdapters(sourceElement)) { var expanded = IsExpanded__AspNetTreeView(sourceElement); TogglePlusMinus__AspNetTreeView(sourceElement, expanded); ToggleChildrenDisplay__AspNetTreeView(sourceElement, expanded); //The function below has been added SetExpandedNodesCookie(sourceElement, expanded); } } //********************************************************************************************************* //Additional code //********************************************************************************************************* function SetExpandedNodesCookie(sourceElement, expanded) { var parentCategory = sourceElement.nextSibling.nextSibling.innerText; var expandedNodesString = ReadCookie("ExpandedTreeNodes"); var expandedNodesArray = new Array(); if(expandedNodesString != null) { if(expandedNodesString.indexOf('|') > 0) { expandedNodesArray = expandedNodesString.split('|'); } else if(expandedNodesString.length > 0) { expandedNodesArray[0] = expandedNodesString; } } CreateExpandedNodesCookie(expanded, expandedNodesArray, parentCategory); }
function CreateExpandedNodesCookie(expanded, expandedNodesArray, parentCategory) { if(!expanded && !ArrayContains(expandedNodesArray, parentCategory)) { expandedNodesArray[expandedNodesArray.length] = parentCategory; CreateCookie("ExpandedTreeNodes", expandedNodesArray.join("|"), 10) } else if(expanded && ArrayContains(expandedNodesArray, parentCategory)) { var newExpandedNodesArray = new Array(); var node; for (index = 0; index < expandedNodesArray.length; index++) { if(RightTrim(parentCategory) != expandedNodesArray[index]) { newExpandedNodesArray[newExpandedNodesArray.length] = expandedNodesArray[index]; } } CreateCookie("ExpandedTreeNodes", newExpandedNodesArray.join("|"), 10); } } function ArrayContains(expandedNodesArray, elementToLookFor) { var elementIndexInArrayString = expandedNodesArray.toString().indexOf(RightTrim(elementToLookFor)); if(elementIndexInArrayString >= 0) { return true; } else { return false; } }
function CreateCookie(name,value,days) { if (days) { var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); } else var expires = ""; document.cookie = name+"="+value+expires+"; path=/"; }
function ReadCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; }
function EraseCookie(name) { createCookie(name,"",-1); }
function RightTrim(sString) { while (sString.substring(sString.length-1, sString.length) == ' ') { sString = sString.substring(0,sString.length-1); } return sString; } //********************************************************************************************************* function GetViewState__AspNetTreeView(id) { var retStr = ""; if ((typeof(id) != "undefined") && (id != null) && (document.getElementById(id) != null)) { var topUL = document.getElementById(id); retStr = ComposeViewState__AspNetTreeView(topUL, ""); } return retStr; }
function ComposeViewState__AspNetTreeView(element, state) { var child = element.firstChild; var bConsiderChildren = true;
// The following line must be changed if you alter the TreeView adapters generation of // markup such that the first child within the LI no longer is the expand/collapse <span>. if ((element.tagName == "LI") && (child != null)) { var expandCollapseSPAN = null; var currentChild = child; while (currentChild != null) { if ((currentChild.tagName == "SPAN") && (currentChild.className != null) && ((currentChild.className.indexOf(collapseClass) > -1) || (currentChild.className.indexOf(expandClass) > -1))) { expandCollapseSPAN = currentChild; break; } currentChild = currentChild.nextSibling; } if (expandCollapseSPAN != null) { if (expandCollapseSPAN.className.indexOf(collapseClass) > -1) { // If the "collapse" class is currently being used then the "collapse" icon is, presumably, being shown. // In other words, the node itself is actually expanded at the present moment (which is why you now // have the option of collapsing it. So we mark it as an "expanded" node for purposes of the view state // we are now accumulating. state += "e"; } else if (expandCollapseSPAN.className.indexOf(expandClass) > -1) { // This part of the tree is collapsed so we don't need to consider its children. bConsiderChildren = false; state += "n"; } } } if (bConsiderChildren && (child != null)) { state = ComposeViewState__AspNetTreeView(child, state); } if (element.nextSibling != null) { state = ComposeViewState__AspNetTreeView(element.nextSibling, state); } return state; }
|
| Girya | Asp.Net User |
| Re: Treeview OnNodeExpanded - OnNodeCollapsed | 2/7/2008 10:38:23 AM |
0/0 | |
|
hi,
Thanks for the code....!
you have used cookies to store the menu state.This state persist even when the user logs out and logs in again. I tried clearing the cookies using Request.Cookies["ExpandedTreeNodes"].Value = "";
Request.Cookies[ "ExpandedTreeNodes"].Values.Clear();
Request.Cookies.Remove( "ExpandedTreeNodes");
Request.Cookies.Clear();
on Sessionstart Event in global.asax.
Any help is appreciated. Please help.
Don't know if this will work.
|
|
| |
Free Download:
Search This Site:
|
|