Wednesday, March 28, 2012

Treeview and HoverMenuExtender

Is there any way to use the HoverMenuExtender with the Treeview? I tried overriding RenderPostText of the treenode and stuffing the extender in, but there's no control collection to stuff it into.

Thanks!

Hi bjpierron,

Unfortunately, I think the answer is no. I was able to get a very, very hacky version to run, but I don't think you'd be able to use it for any real scenarios. I don't think thatTreeView, and specificallyTreeNode, are extensible enough for you to add this support on top. With some really creative CSS you might be able to get the effect you're looking for using theHoverNodeStyle, but that's bout all I can suggest.

If you were interested, here's how I kind of got it not quite working... I created my ownHoverMenuTreeNode class that inherited offTreeNode. I added aPopupControlID property and variable toHoverMenuTreeNode to store the ID of the control to hover. In itsRenderPreText method I then 1) got a reference to the current page viaHttpContext.Current.CurrentHandler as Page, 2) checked the page's controls recursively to make sure a HoverMenuExtender was included (this ensures the necessary script is downloaded to the client) or I throw an exception that you need aHoverMenuExtender on the page when usingHoverMenuTreeNode, 3) got a reference to thepopupControl usingPage.FindControl and thePopupControlID variable, 4) used theHtmlTextWriter to write the start of adiv tag including a random id (I just used a GUID), 5) created a script that when executed did the exact same things as the JavaScript inthis post after a 1000 ms timeout (using the randomly generated ID and thepopupControl.ClientID), and 6) registered the script as a startup script with the page. Then in theRenderPostText I wrote out the end tag of thediv. What we're essentially doing is wrapping theTreeNodein adiv and dynamically hooking up theHoverMenuBehavior to it. This whole thing looks a lot messier than just adding controls to the page, but I couldn't find a way to do that usingTreeNode (as it's not aControl). The reason that this will never work reliably in practice is that we can't run our code to wireup the behavior until the "Atlas" scripts and our HoverMenuBehavior.js have run. This is why I added the timeout on the script to prevent it from running for a second to see if I could get it working... but it's not a safe thing to do in practice. Here's the code:

// WARNING: THIS IS VERY BAD. IT IS PRESENTED FOR LEARNING PURPOSES ONLY.// PLEASE DO NOT USE THIS CODE IN ANY APPLICATION YOU WRITEusing System;using System.Collections.Generic;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;using AtlasControlToolkit;namespace ToolkitDemo{/// <summary> /// The HoverMenuTreeNode provides HoverMenu support on top of a standard /// TreeNode. You must have a TreeNode extender on your page for this to work. /// </summary>public class HoverMenuTreeNode : TreeNode {/// <summary> /// ID of the control that will display when this node is hovered /// </summary>private string _popupControlID =null;/// <summary> /// ID of the control that will display when this node is hovered /// </summary> [IDReferenceProperty(typeof(Control))]public string PopupControlID {get {return _popupControlID; }set { _popupControlID =value; } }/// <summary> /// Wrap the TreeNode in a div and write a startup script to wireup /// a HoverMenuBehavior to that div to display our the PopupControl /// </summary> /// <param name="writer">Html Writer</param>protected override void RenderPreText(HtmlTextWriter writer) {// Get a reference to the current page Page page = HttpContext.Current.CurrentHandleras Page;if (page ==null)throw new NotSupportedException("HoverMenuTreeNode can only be used in System.Web.UI.Page");// Recursively check to ensure the page contains a hovermenucontrolbool foundExtender =false; Queue controls =new Queue(); controls.Enqueue(page);while (controls.Count > 0) { Control current = controls.Dequeue(); foundExtender = currentis HoverMenuExtender;if (foundExtender)break;foreach (Control ctrlin current.Controls) controls.Enqueue(ctrl); } controls.Clear();if (!foundExtender)throw new NotSupportedException("HoverMenuTreeNode requires a HoverMenuExtender present on the Page");// Get a reference to the PopupControl Control popupControl = page.FindControl(_popupControlID);if (popupControl ==null)throw new NotSupportedException("HoverMenuTreeNode could not find a control with id \"" + _popupControlID + "\"");// Write out the start of the div that wraps the TreeNodestring id = Guid.NewGuid().ToString(); writer.WriteBeginTag("div"); writer.WriteAttribute("id", id); writer.Write(">");// Create a script that dynamically associates a new HoverMenuBehavior // with the id of our new div and the ClientID of the popupControl and // register it as a startup scriptstring script =// Wrap everything in a function to keep things uncluttered"<script>(function() { " +// Wrap the wireup in a function to execute on a timeout"var f = function() {" +"var ctrl = new Sys.UI.Control($('" + id +"')); " +"var behavior = new AtlasControlToolkit.HoverMenuBehavior(); " +"behavior.set_PopupControlID('" + popupControl.ClientID +"'); " +"behavior.set_PopupPosition('Right'); " +"ctrl.get_behaviors().add(behavior); " +"behavior.initialize(); " +"}; " +// Wait 1 second before running the function"window.setTimeout(f, 1000); " +"})();</script>"; page.RegisterStartupScript(id, script);// Write out the TreeNodebase.RenderPreText(writer); }/// <summary> /// Write the end of the div that wraps the TreeNode /// </summary> /// <param name="writer">Html Writer</param>protected override void RenderPostText(HtmlTextWriter writer) {base.RenderPostText(writer); writer.WriteEndTag("div"); } }}

Thanks,
Ted


Could most of this plumbing work be handled in an Adapter (ala the CSSAdapters)? Then you could change the way the actual TreeNode is rendered and then implement HoverTreeView class as a derivative of the normal TreeView which could be targetted by the normal extender stuff.

Hi IDisposable,

Yep, a lot of the plumbing goes away if you inherit from a TreeView. I don't even think you'd need to use an adapter. You could dynamically add theHoverMenuExtender to itsControls collection, etc. I believe you'd still be stuck with the problem of wiring up theTargetControlID of theHoverMenuProperties because it needs to be a server control. Not to mention the larger issue of associating any of the content in yourHoverMenu with a givenTreeNodefor any real functionality, etc. Aside from hacking something very specific together, I doubt you'd be able to make a solid, general purpose solution.

Thanks,
Ted


Has anyone come up with a workable way to do this yet? I played around with adding a HoverMenuExtender to a TreeNode programmatically but quickly found out that the TreeNode does not have an ID that I could access. So, therefore, I didn't have anything to poke into the TargetID property of the HoverMenuExtender.

No comments:

Post a Comment