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.

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

33 comments:

Anonymous said...

This is exactly what I was looking for. The code works beautifully.

Thanks

Unknown said...

Thank you, worked perfectly! Have been looking for a code that can do postback only when checkbox is clicked and that works for firefox for quite some time now. By changing the src.tagName... to "input" instead of "a" it worked :D

Unknown said...

Hi,
this works really well, but not in my case...

I've set the SelectAction property of the TreeNodes to None (as I don't want that anything happens when I click in a node) and there is no < a > tag and no href property with javascript:__doPostBack(...) value...

The only thing that exists is a < span > with class and id (none of them with the Value that I've set programmaticaly)...

In this case is there any chance to get the Value through Javascript?

Pushpendra said...

I'm afraid, there is no chance to get the treenode value on the client in the current scenario, because the value is not rendered on the client :(

However there is a hack to get around this if you absolutely want the value on the client click of the node, try this:

--------------------------------------
1)Ensure that the node value is rendered on client. Set the node text as below:

string nodeText = "Nuno";
string nodeValue = "Gomez";
TreeNode myTreeNode = new TreeNode();
myTreeNode.SelectAction = TreeNodeSelectAction.None;
myTreeNode.Text = string.Format("<span onclick='getNodeVal(this)' nodeVal='{1}'>{0}</span>",nodeText,nodeValue);
tree.Nodes.Add(myTreeNode);

2)use the js function
function getNodeVal(span)
{
alert(span.nodeVal);
//return span.nodeVal;
}
-------------------------------

HTH,
pushp

Unknown said...

Hi,
just a small correction to your code: the javascript function should be
function getNodeVal(span)
{
alert(span.getAttribute('nodeVal'));
}

The way you had it didn't work with firefox. ;-)

Now, I've a couple more of comments...
My treeview has checkboxes on every node and I'm using your own script to check/uncheck parents and childs (and it's working great!).
The way this part is done, the js is being called when I click on the node and not on the checkbox which was what I really wanted...
The perfect situation would be to have access to the node value inside the function to checo/uncheck parents and childs...

Also, the way your code is, the < span onclick='getNodeVal(this)'... > is appearing as tool tip text when I put the mouse over the checkbox. The tool tip of the text node is still correct, though (I've set it with node.ToolTip property on the server side).

Can you do something for these issues? :-)

And thanks for all your help :-)

Pushpendra said...

Thanks for the correction. :-)

To fix your issues, try this:

-----------------------------------------------
1)Add another attribute to treeview in page load as:

tree.Attributes.Add("onmouseover", "OnTreeMouseOver(event)");

2)Use below script:

function OnTreeMouseOver(evt)
{
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var isChkBoxHover = (src.tagName.toLowerCase() == "input" && src.type == "checkbox");
if(isChkBoxHover)
{
var label = getNextSibling(src).getElementsByTagName("span")[0];
var nodeText = label.innerHTML;
//var nodeVal = label.getAttribute('nodeVal');
src.title = nodeText;
}
}
function getNextSibling(element)
{
var nextSib = element.nextSibling;
while(nextSib.nodeType != 1)
{
nextSib = nextSib.nextSibling;
}
return nextSib;
}
--------------------------------------

you can get the node value in the 'OnTreeClick' function in the same way i got in the 'OnTreeMouseOver' function (commente line)

HTH,
Pushp

Anonymous said...

Hi there,

I'm a c# developer, haven't done much with ASP and JS.

This was exactly what I was looking for, and it works great...

thanks a lot!

Anonymous said...

thanks; been looking for a solution like this all day!!!

Anonymous said...

thank you very much, I just used it in one of my projects. src.innerText throws an exception on FF btw. I replaced it with innerHTML instead. Thanks again.

Anonymous said...

I believe OnMouseOver code won't work.. Reason it won't work is because
(
window.event != window.undefined ? window.event.srcElement : evt.target
)

code works fine only when one
clicks on node item and does not
work when you select checkbox..

Anonymous said...

Ok My bad.. I forgot to put span code when populating each node..
Works like champ. Thanx and sorry about that.. !

Frankie said...

Thank you for the solution - I'm still having some issues and I will apreciate any help.

I have a TreeView with CheckBoxes and with the third level dynamically populated. I used the TreeNode.Text and put a Span in it (with a nodeValue attribute).

My problem is apparently related to AJAX. When i try to do any postbacking I have an error message: (ready for it?) Sys.WebForms.PageRequestManagerServerErrorException with code 500.

I think the problem is with the '<' and '>' and the partial rendering. I tried to replace them with > and < and didn't get the exception - but that way i'm losing the ability to navigate to the Span element.

Help?

thanks,
Frankie

Pushpendra said...

Hi Frankie,

Try this:

Code Behind:
--------------------------------
TreeView1.Attributes.Add("onmouseover", "OnTreeMouseOver(event)");
TreeView1.Attributes.Add("onmouseout", "OnTreeMouseOut(event)");

and for nodes:

string nodeText = "My Tree Node";
string nodeValue = "Node Value";
TreeNode myTreeNode = new TreeNode(nodeText);
myTreeNode.ToolTip = nodeValue; //store value in tooltip
myTreeNode.SelectAction = TreeNodeSelectAction.None;
TreeView1.Nodes.Add(myTreeNode);
--------------------------------

Javascript:
--------------------------------
var tempNodeVal = ''; //global var
function OnTreeMouseOver(evt) {
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var isNodeOver = src.tagName.toLowerCase() == "span";
if (isNodeOver) {
tempNodeVal = src.title; //temporarily store node val before blanking it
src.title = '';
}
}

function OnTreeMouseOut(evt) {
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var isNodeOver = src.tagName.toLowerCase() == "span";
if (isNodeOver)
src.title = tempNodeVal; //reassign the node val to title, will need it
}

function GetCSVListOfCheckedNodeValues(treeID) {
var inputsInTree = document.getElementById(treeID).getElementsByTagName("input");
var CSVArray = new Array();
for (var i = 0; i < inputsInTree.length; i++) {
var currentElement = inputsInTree[i];
if (currentElement.type == "checkbox" && currentElement.checked)
CSVArray.push(GetNextSibling(currentElement).title);
}
return CSVArray.join(','); //this is comma separated string of checked node values
}
function GetNextSibling(currentObject)
{
var nxt = currentObject;
do nxt = nxt.nextSibling;
while (nxt && nxt.nodeType != 1);
return nxt;
}
-------------------------------

Explanation:
This solution is again a HACK. I'm storing node value in node's tooltip and using mouse events to prevent it from showing up on node hover. Use the GetCSVListOfCheckedNodeValues function to get a comma seperated string of checked node values. That's all.

Hope this helps.

Asha said...

Hi,

I have 3 report names listed in a value prompt designed using the Javascript. Namely 'Summary', 'Trend' and 'Detail'. So my default report is 'Summary'. Along with this value Prompt, I have a Tree Prompt created with a parameter 'temp_group' with Name 'aTree'(In Cognos ReportStudio). So Once I select the nodes in the Tree Prompt and click submit, a summary report is created.

Now I move to the second report (Trend) by clicking the value prompt option. Now how do I pass the selected 'Tree Nodes' from first report to second report and so on... Or How do I stop the nodes from being deselected when I move from first report to second report....

PS: My value prompt is based on a javascript... calling respective reports when I select the option in the prompt is written in javascript( by calling the report URL). I have a Tree prompt added to all the 3 reports menioned above. So how do I pass the selected 'Tree Nodes' from first report to second report and so on... So a javascript for this would help me in this.....Can anyone help me?

Hopes this helps you to understand my requirement...

Regards

Pushpendra said...

Hi Asha,

I'm not sure how things work in cognos report studio but here is a general approach: keep a global hidden variable (<input type="hidden".../> for each of your tree prompts and when you select/deselect a node from a particular tree prompt, update the respective hidden variable's value by appending/removing the newly selected node's value (use comma as a delimier). Now you can access the hidden variable's value for a specific report and get the individual values by splitting (http://www.quirksmode.org/js/strings.html#split).

Regarding disabling nodes from the pervious report, write a function that runs on the window load and read the selected values from the correct hidden variable and loop through the tree prompt nodes and disable the node values that are present in the hidden variable for the other report.

Hope this gives an idea.

Asha said...

Hi Pushp,

Thanks for your prompt response. Please analyse the codes below....

Let me explain u in detail..

I have Tree prompt with 2 button Submit and Cancel ( this is common on all 3 reports). So I select some nodes and Click submit, report is generated. Next I select the second report. Now I need the Tree prompt values intact from the first report.

Codes for Submit and Cancel:

function treeSubmit()
{
// First thing ... check if tree has selected values in order to
// determine if the user has selected or deselected.
var selectedTreeNode = treeaTree.getLastSelectedNode();

// This means that there's a selected value
if (selectedTreeNode != null)
{
docName._oLstChoicestreeSubmitOk.value = 1;
docName._oLstChoicesMeter_Select_Prompt[1].text='Temporary Selection';
docName._oLstChoicesMeter_Select_Prompt[1].selected = true;
document.getElementById('tree_prompt').style.display='none';
docName._oLstChoicesMeter_Select_Prompt.style.visibility = 'visible';

if (docName._oLstChoicesReport_Select_Prompt.value == 'PostageSummary')
{
document.getElementById('outputSelector').style.display='block';
docName._oLstChoicesLeft_Measure_Prompt.style.visibility = 'visible';
docName._oLstChoicesRight_Measure_Prompt.style.visibility = 'visible';
}

SetPromptControl('reprompt');
}
else
{
docName._oLstChoicestreeSubmitOk.value = 0;
docName._oLstChoicesMeter_Select_Prompt[0].selected = true;
document.getElementById('tree_prompt').style.display='none';

if (docName._oLstChoicesReport_Select_Prompt.value == 'PostageSummary')
{
document.getElementById('outputSelector').style.display='block';
docName._oLstChoicesLeft_Measure_Prompt.style.visibility = 'visible';
docName._oLstChoicesRight_Measure_Prompt.style.visibility = 'visible';
}

SetPromptControl('reprompt');

}
}

function closeTree()
{
deSelectCustomTree();
document.getElementById('tree_prompt').style.display='none';

if (docName._oLstChoicesReport_Select_Prompt.value == 'PostageSummary')
{
document.getElementById('outputSelector').style.display='block';
docName._oLstChoicesLeft_Measure_Prompt.style.visibility = 'visible';
docName._oLstChoicesRight_Measure_Prompt.style.visibility = 'visible';
}
}

---------

As I said I have 3 separate reports:

Code of How I call the reports:

function changeReport(report)

{

//The below line disables the "Apply Date Range" Button once the report is selected from the report selection box.
document.getElementById('hitme').disabled = true;
if (report == 'PostageTrend')
{ deSelectCustomTree()

//target_url="http://"+serverNameURL+"/cognos8/cgi-bin/cognosisapi.dll?b_action=xts.run&m=portal/report-viewer.xts&async.primaryWaitThreshold=0&ui.action=run&ui.object=%2fcontent%2ffolder%5b%40name%3d%27DevFolder%27%5d%2ffolder%5b%40name%3d%27Reports%20ver2%27%5d%2freport%5b%40name%3d%27postage_summary_report%27%5d&cv.ccurl=1&ui.header=false&ui.toolbar=false";
target_url="http://"+serverNameURL+"/cognos8/cgi-bin/cognosisapi.dll?b_action=xts.run&m=portal/report-viewer.xts&async.primaryWaitThreshold=0&ui.action=run&ui.object=%2fcontent%2ffolder%5b%40name%3d%27DevFolder%27%5d%2ffolder%5b%40name%3d%27MSWReports%27%5d%2freport%5b%40name%3d%27postage_trend_report%27%5d&cv.ccurl=1&ui.header=false&ui.toolbar=false";

}
else if (report == 'PostageClass')
{ deSelectCustomTree()

//target_url="http://"+serverNameURL+"/cognos8/cgi-bin/cognosisapi.dll?b_action=xts.run&m=portal/report-viewer.xts&async.primaryWaitThreshold=0&ui.action=run&ui.object=%2fcontent%2ffolder%5b%40name%3d%27DevFolder%27%5d%2ffolder%5b%40name%3d%27Reports%20ver2%27%5d%2freport%5b%40name%3d%27postage_class_report%27%5d&cv.ccurl=1&ui.header=false&ui.toolbar=false";
target_url="http://"+serverNameURL+"/cognos8/cgi-bin/cognosisapi.dll?b_action=xts.run&m=portal/report-viewer.xts&async.primaryWaitThreshold=0&ui.action=run&ui.object=%2fcontent%2ffolder%5b%40name%3d%27DevFolder%27%5d%2ffolder%5b%40name%3d%27MSWReports%27%5d%2freport%5b%40name%3d%27postage_class_report%27%5d&cv.ccurl=1&ui.header=false&ui.toolbar=false";

}

to_set = target_url;

// URL parameters in form of p_ParameterName
to_set += '&p_firstRun=';
to_set += escape(docName._oLstChoicesdefaults.value);
to_set += '&p_date_filter_parameter=';
to_set += escape(docName._oLstChoicesDFP.value);
to_set += '&p_meter_group=';
to_set += escape(docName._oLstChoicesMeter_Select_Prompt.value);
to_set += '&p_day1=';
to_set += escape(docName._textEditBoxday1.value);
to_set += '&p_day2=';
to_set += escape(docName._textEditBoxday2.value);
to_set += '&p_month1=';
to_set += escape(docName._textEditBoxmonth1.value);
to_set += '&p_month2=';
to_set += escape(docName._textEditBoxmonth2.value);
to_set += '&p_month_numerical1=';
to_set += escape(docName._textEditBoxmonth_numerical1.value);
to_set += '&p_month_numerical2=';
to_set += escape(docName._textEditBoxmonth_numerical2.value);
to_set += '&p_year1=';
to_set += escape(docName._textEditBoxyear1.value);
to_set += '&p_year2=';
to_set += escape(docName._textEditBoxyear2.value);


if (docName._oLstChoicestreeSubmitOk.value != 0)
{
to_set += document.getElementById("meter_temp_group").innerHTML;
}

window.location.href=to_set;

}

-------

Thanks
Asha

Anonymous said...

This code examples list here are great. But I have a somewhat unique task. What I would like to do is capture the mouseover event for the Parent nodes in the treeview and pop-up a bubble window, not an alert but a bubble window similar to hover window that pops up for a point on a map. Any thoughts?

Anonymous said...

Hi there, i dont know what im doing wrong but im not managing to get the text of the node when i click the checkbox near it.

i followed ure example:

in C# code behind:
TreeView1.Attributes.Add("OnClick", "return client_OnTreeNodeChecked(event)");

and on client side- javascrip:
function client_OnTreeNodeChecked(evt)
{
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var nodeClick = src.tagName.toLowerCase() == "input";
alert(nodeClick);//returns true.
if(nodeClick)
{
var nodeText = src.innerText;
alert("Text: "+nodeText);
}
//return false; //uncomment this if you do not want postback on node click
}

please help.

Unknown said...

PUSHP, Thanks a lot - it works for me. this is what i am looking for.

then, reply to Anonymous (Feb 2 2009):
comment the first alert before the if condition

Pushpendra said...

Hi Anonumous (Feb 2, 2009),

You need to get the node to get its text which is next to the checkbox. Use below script:

------------------------------
function client_OnTreeNodeChecked(evt) {
var src = window.event != window.undefined ? window.event.srcElement : evt.target;
var isCheckBoxClick = src.tagName.toLowerCase() == "input" && src.type == "checkbox";
if (isCheckBoxClick) {
//check for src.checked if you need text only on checkbox checked

//innerText works in IE and fails in Firefox, so use innerHTML if first call fails
var nodeText = getNextSibling(src).innerText || getNextSibling(src).innerHTML;
alert("Text: " + nodeText);
}
}
function getNextSibling(element) {
var n = element;
do n = n.nextSibling;
while (n && n.nodeType != 1);
return n;
}
---------------------------------

HTH,
Pushp

Anonymous said...

Well I just wanna say, AWESOME. I changed it abit to make it work on my own page but it works PERFECT, you wouldnt believe how many hours Ive spent with this!!!!

So.... THANKYOU!!! :)

Unknown said...

Hi,

Dear I have on Problem regarding your code. In code behind file I have added in Page_Load Method but the code is not going to javascript code!!

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

Please help me in this matter.

Unknown said...

Hi,
My Problem is solved! Now code is working Thanks for a nice code!!

Anonymous said...

Hi there...
Thanks for this snippet which is what I was looking for...

But I'm having an issue in expanding the tree nodes. I don't want the Postback. So, I uncommented the "return false;". But it doesn't allow the expand action also... I need to expand & collapse the nodes...

Using IE 6...

Any solution/comments would be appreciated...

Anonymous said...

Just put the "return false;" into the "if"-statement:

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

Unknown said...

On a button click of a report studio prompt page I would like to get the Treeprompt Last selected node popup using Javascript, How do i do it?

Anonymous said...

I want to quote your post in my blog. It can?
And you et an account on Twitter?

Jony Shah said...

Hi All,

I have used this logic and i got value on client side and its working fine.

but problem is i am not able to expand parent node.so i am not able to see child nodes.

can you please tell me whts problem is ?
or how to resolve this problem ?

Anonymous said...

Good Day i'm fresh here. I hit upon this chat board I have found It extremely accommodating and it has helped me loads. I should be able to contribute and aid other people like its helped me.

Thanks, See You Around

Prakash N said...

Super .. this is what i was looking for .. thanks a lot.

hlpearl said...

GREAT solution! It's simple and easy (and works!)

Ven said...

Hi,
I need a help here. I am adding nodes to my treeview through code. For every node, I am setting the text and navigate url properties. Hence the SelectedNodeStyle doesn't get applied. Please help.

Pushpendra said...

Hi Ven,

The question is more suited for asp.net forum: http://forums.asp.net

but I will take a stab. I tried what you described and it works for me. Here is what I'm doing -

.aspx

<asp:TreeView ID="tree" SelectedNodeStyle-BackColor="Green" ShowLines="true" runat="server" >
</asp:TreeView>

.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//add root node
tree.Nodes.Add(GetTreeNode("Root"));

for (int i = 0; i < 6; i++)
{
tree.Nodes[0].ChildNodes.Add(GetTreeNode(string.Format("Node {0}", i)));
}
tree.Nodes[0].ChildNodes[3].Selected = true;
}
}

private TreeNode GetTreeNode(string text)
{
var node = new TreeNode(text);
node.NavigateUrl = "http://www.google.com";
return node;
}

I'm sure you are doing something different.

- pushp