Multi-node tree picker source code from CodeGarden 2010

by Shannon Deminick 29. June 2010 14:54

If you missed out on CodeGarden 2010 this year then you definitely missed one heck of a conference! If you want to read more about what happened you can check out these posts here:

This post is about the data type I presented during the Umbraco package competition (which ended up winning too! :). It’s a multi-node picker with the full tree interface to allow you to select the nodes you want. It’s also sort-able with drag/drop functionality.

image

It’s a very simple control but has the capability to be made into a very awesome data type which is why I’m starting up a new CodePlex project called: Data Types for Umbraco (found here). I’m hoping that people in the Umbraco community will want to become developers on this project so a bunch of us can collaborate to create some seriously amazing data types for Umbraco 4.5. Please let me know if you want to contribute to this project as i think it could have huge potential. Instead of everyone creating their own closed source data types and developing them all in different ways, this would help unify and standardize the way we create data types.

So the first thing I will put up there is the full source for this data type as it was meant to be. However, I’m not going to get around to that today, so in the meantime I’ve put the source for this control right here. Remember, this is not really the best way to make this data type but it will give you a good indication of how to use the new JavaScript tree API and how to build a simple UserControl data type.

Enjoy!

ASCX Code

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TreePicker.ascx.cs"
    Inherits="ExamineDemo1.usercontrols.TreePicker" %>
<%@ Register TagPrefix="umb" TagName="tree" Src="~/umbraco/controls/Tree/TreeControl.ascx" %>
<script type="text/javascript">

//a reference to the hidden field
//storing the selected node data
var hiddenField = jQuery("#<%=PickedNodes.ClientID%>");

//create a sortable, drag/drop list and
//initialize the right panel with previously
//saved data.
jQuery(document).ready(function () {
    jQuery(".right").sortable({
        stop: function (event, ui) { StorePickedNodes(); }
    });
    jQuery(".item a.close").live("click", function () {
        jQuery(this).parent().remove();
    });

    //now rebuild the selected items
    try {
        var json = eval('(' + hiddenField.val() + ')');
        jQuery(".right").html(unescape(json.markup));
        StorePickedNodes();
    }
    catch (err) { };
});

//Add event handler to the tree API.
//Bind to the window load event as the document ready event
//is too early to query for the tree api object
jQuery(window).load(function () {
    //add a handler to the tree's nodeClicked event
    jQuery("#<%=TreePickerControl.ClientID%>")
        .UmbracoTreeAPI()
        .addEventHandler("nodeClicked", function (e, node) {
            AddToRight(node);
    });
});

function AddToRight(node) {
    //get the node id of the node selected
    var nodeId = jQuery(node).attr("id");
    //check if node id already exists in the right panel
    if (jQuery(".right").find("li[rel='" + nodeId + "']").length > 0) {
        return;
    }
    //create a copy of the node clicked on the tree
    var jNode = jQuery(node).clone().find("a:first")
    //remove un-needed attributes
    jNode.removeAttr("href")
        .removeAttr("umb:nodedata")
        .attr("href", "javascript:SyncItems(" + nodeId + ");");
    //build a DOM object to put in the right panel
    jQuery("<div class='item'><ul class='rightNode'>" +
            "<li rel='" + nodeId + "' class='closed'>" +
            "</li></ul><a class='close' href='javascript:void(0);'>[X]</a></div>")
        .appendTo(".right")
        .find(".closed").append(jNode);
    //now update the hidden field with the
    //node selection
    StorePickedNodes();
}

//A method to sync the left tree to the item
//selected in the right panel
function SyncItems(nodeId) {
    jQuery("#<%=TreePickerControl.ClientID%>")
        .UmbracoTreeAPI()
        .syncTree(nodeId.toString());
}

//were going to store both the node ids
//and the html markup to re-render the 
//right hand column as JSON to be put into
//the database.
function StorePickedNodes() {
    var val = "[";
    jQuery(".right .item ul.rightNode li").each(function () {
        val += jQuery(this).attr("rel") + ",";
    });
    if (val != "[") val = val.substr(0, val.length - 1);
    val += "]";
    //append the html markup
    var obj = "{ \"val\": " + val + ", \"markup\": \"" + escape(jQuery(".right").html()) + "\"}";
    hiddenField.val(obj);       
}

</script>

<%--Inline styles are dodgy, but simple for
this demonstration--%>

<style type="text/css">
.multiTreePicker .item ul.rightNode 
{
    float:left;
    margin:0;
    padding:0;    	
}
.multiTreePicker .item ul.rightNode li
{
    margin:0;
    padding: 0;
    list-style:none;
    font:icon;
    font-family:Arial,Lucida Grande;
    font-size:12px;
    min-height:20px;
}
.multiTreePicker .item ul.rightNode li a
{
    background-repeat:no-repeat !important;
    border:0 none;
    color:#2F2F2F;
    height:18px;
    line-height:18px;
    padding:0 0 0 18px;
    text-decoration:none;
}
.multiTreePicker .item a
{
    float:left;
}   
.multiTreePicker .item a.close 
{
    margin-left:5px;
}
.multiTreePicker .item
{
    cursor: pointer;
    width:100%;
    height:20px;
}
.multiTreePicker .left.propertypane
{
    width: 300px;
    float: left;
    clear:none;
    margin-right:10px;
}
.multiTreePicker .right.propertypane
{
    width: 300px;
    float: left;
    clear:right;
    padding:5px;
}
.multiTreePicker .header
{
    width:622px;
}
   
</style>

<div class="multiTreePicker">
    <div class="header propertypane">
        <div>Select items</div>
    </div>
    <div class="left propertypane">        
        <umb:tree runat="server" ID="TreePickerControl" 
            CssClass="myTreePicker" Mode="Standard" 
            DialogMode="id" ShowContextMenu="false" 
            IsDialog="true" TreeType="content" />
    </div>
    <div class="right propertypane">
    </div>
</div>

<asp:HiddenField runat="server" ID="PickedNodes" />

C# Code Behind


public partial class TreePicker : 
    System.Web.UI.UserControl, IUsercontrolDataEditor
{
        


    #region IUsercontrolDataEditor Members

    public object value
    {
        get
        {
            //put the node in umbraco
            return PickedNodes.Value;
        }
        set
        {
            //get from umbraco
            PickedNodes.Value = value.ToString();
        }
    }

    #endregion
}
Categories: .Net | Umbraco

Comments

7/1/2010 8:21:19 PM #

Love it! I have however had to make one small change to get it to work...
I have changed

var hiddenField = jQuery("#<%=PickedNodes.ClientID%>");

//create a sortable, drag/drop list and
//initialize the right panel with previously
//saved data.
jQuery(document).ready(function () {


To:

var hiddenField;

    //create a sortable, drag/drop list and
    //initialize the right panel with previously
    //saved data.
    jQuery(document).ready(function ()
    {

        hiddenField = jQuery("#<%=PickedNodes.ClientID%>");


Otherwise the hiddenField doesn't work as the DOM hasn't been loaded at that point.

Ivan United Kingdom

7/2/2010 3:01:24 PM #

Actually, I have made some more changes.

Rather than storing the generated markup (as this won't handle content being renamed), I have changed it to store only the IDs as XML, and then actually load the node titles at the point it is loaded.

ascx here:
http://tinypaste.com/c366c

and ascx.cs here:
http://tinypaste.com/239ad

Ivan United Kingdom

7/4/2010 10:30:31 AM #

Nice!
yeah, storing the markup is pretty dodgy i know, but needed a quick way to be able to store the icons, etc... for a demo :) I'll be updating the real control in  the uComponents source today to use a version very similar to yours!
http://ucomponents.codeplex.com

Test Australia

7/10/2010 4:11:01 AM #

Need example XSLT that shows how to retrieve the selected nodes.

Also how the selected nodes are stored in the database.

Daniel Bardi United States

7/10/2010 2:19:15 PM #

Please, please please make this into a package for those of us less able to deal with code :)

Tim United Kingdom

7/11/2010 2:00:26 PM #

BUG: selected nodes are stored in CDATA tags.. not standard.
...so I wrote up some XSLT to read the selected items.

Read it here: www.danielbardi.com/.../...i-Node-Tree-Picker.aspx

www.danielbardi.com
www.dascoba.com

Daniel Bardi United States

7/12/2010 11:10:54 AM #

I would like to contribute a couple of datatypes to this project.  Would you allow me to be a contributing developer?

Daniel Bardi United States

7/12/2010 11:22:51 AM #

You should contact the guys through the codeplex project website - http://ucomponents.codeplex.com/

AaronPowell United States

7/28/2010 3:16:28 PM #

Hi Shannon,

First of all, congratulations on all your great work.

I am trying to expand your code and I'm hitting a snag when trying to set a start node id on the Umbraco Tree Tree control ("~/umbraco/controls/Tree/TreeControl.ascx).

There's a StartNodeID property that does nothing, I've tried calling SetTreeService with my own StartNodeID and also nothing. Basically, it just gets ignored and the Tree is always rendered starting from the "Content" folder.

Any help/guidance?

Thanks,
Emanuel

Emanuel Gaspar Portugal

7/29/2010 5:30:49 PM #

Hi,

When I try to put more than one multi picker tree in a document i get an error saying that i have controls with duplicate ID's.

Anyone know if there is a fix planned to support multiple tree picker controls in a single document?

Thanks

SimonY United Kingdom

7/29/2010 5:59:42 PM #

Just to follow up, Kipu wrote a patch for the "multiple tree picker in one document" problem:

ucomponents.codeplex.com/.../PatchList.aspx

SimonY United Kingdom

8/3/2010 4:32:12 AM #

In regards to StartNodeId, this is due to the underlying process of the content tree base class and the user's security.
Now that i am back from holidays, I'll be updating this control to work properly with filtering, etc... and the only way to do this is the create a custom c# tree that extends the content tree (which will make the start node id work... but this also needs to check for security too).

ShannonDeminick United States

8/3/2010 1:25:13 PM #

Ok, I see. Thanks for the reply.

I have a working multiple picker data type that:
- support multiple instances within the same page
- stores the ids as XML as actual XML within the document and not with CDATA wrapped around it
- I render the IDs as nodes on the fly and not by storing the html within the data.

So as you can see, the last hurdle before I could release this as a package is the start node id working, which apparently doesn't. I looked at the source code for umbraco, as well as the javascript, and I couldn't understand how I would set the start node id. Oh well.

Emanuel Gaspar Portugal

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading