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

A Few New Controls in Umbraco 4.1

by Shannon Deminick 19. January 2010 17:07

Currently the codebase in Umbraco 4.0.x uses quite a few iframes to render ‘controls’ as this functionality has existed this way from way back in the day and had never been upgraded… until now!

IFrames should be used sparingly, they have their uses but to render an iframe instead of a User Control is just adding overhead to the page, the client and server cpu/memory consumption and is not so cool. Here’s a nice benchmark on iframe performance: http://www.stevesouders.com/blog/2009/06/03/using-iframes-sparingly/ . As you can see, you should use iframes ONLY when completely necessary.

So on to the good news… Here’s some new controls that have been created in 4.1 (which will remove many of these iframes and make things a whole lot nicer to use)

  • /umbraco/controls/Tree/TreeControl.ascx
    • Will render a new tree based on the properties set
    • This makes the following Obsolete: treeInit.aspx, treePicker.aspx  (though, these pages are still used to load trees in modal windows for pickers but shouldn’t be used directly in your code)
      • Both of these pages now simply wrap the TreeControl
    • Example usage:
      <umb2:Tree runat="server" ID="DialogTree" 
          App="media" TreeType="media" IsDialog="true" 
          CustomContainerId="TinyMCEInsertImageTree" ShowContextMenu="false" 
          DialogMode="id" FunctionToCall="dialogHandler" />

    • There’s quite a few other properties that allow you to customize the tree to your needs
    • There’s also a very in-depth JavaScript API
  • /umbraco/controls/Images/ImageViewer.ascx
    • This is a nifty ajax control that will take a media item id and display the image
    • There’s a simple JavaScript library attached to it that allows you to dynamically update the media id to force an ajax request to refresh the image (amongst other methods)
    • This makes the following Obsolete: /umbraco/dialogs/imageViewer.aspx
      • The old codebase for imageViewer has been retained (though it should probably just wrap this control :)
    • Example usage:
      <umb3:Image runat="server" ID="ImageViewer" 
          ViewerStyle="ThumbnailPreview" LinkTarget="_blank" 
          ClientCallbackMethod="onImageLoaded" />

  • /umbraco/controls/UploadMediaImage.ascx
    • This control is essentially what you see when you load up TinyMCE, select the insert image button, then click on the ‘Create New’ tab. It contains the logic to enter a name, select a file to upload and select the media tree node to upload it to.
    • There’s a handy JavaScript callback method you can define so that it’s executed once the upload is complete. Tons of parameters are passed to the callback containing all of the information about the file/image.
    • This makes the following Obsolete: /umbraco/dialog/uploadImage.aspx
    • Example usage:
      <umb4:MediaUpload runat="server" ID="MediaUploader" 
          OnClientUpload="onFileUploaded" />

Now, on to the ‘pickers’! There’s quite a few picker controls in the codebase that all essentially do the same thing but the code for them was pretty much replicated everywhere, so i decided to streamline the whole thing which should make it quite easy for anyone to make their own pickers!

  • umbraco.controls.BaseTreePicker.BaseTreePicker (in the umbraco.controls assembly)
    • (yes i know the namespace and control are the same name, but that’s the way it is currently! :)
    • From the name, you would probably determine that this control is an abstract control… and you’d be correct.
    • This control implements: IDataEditor (so that it can be used as the data editor for Umbraco data type controls), and INamingContainer for obvious reasons.
    • This control exposes many properties and methods for you to modify and override to customize the picker.
    • The abstract properties are ModalWindowTitle (the title of the window that gets displayed) and TreePickerUrl (the URL to load in the modal window that is displayed)
    • This pretty much handles everything for a basic tree picker and the JavaScript has been refined to use real classes! Wow! ;)
  • umbraco.editorControls.mediaChooser (in the umbraco.editorControls assembly)
    • This is the umbraco data type to select a media item from the media tree
    • It’s been upgraded to inherit from BaseTreePicker and overrides the JavaScript rendered to support Tim’s new fandangled media picker (similar to the TinyMCE media picker)
  • umbraco.editorControls.pagePicker (in the umbraco.editorControls assembly)
    • This is the umbraco data type to select a content node from the content tree
    • It’s been upgraded to inherit from BaseTreePicker … it really doesn’t have any special functionality apart from setting the title and the tree picker url since all of the required functionality is in the BaseTreePicker
  • umbraco.controls.ContentPicker (in the umbraco assembly)
    • This pretty much does the same thing as all of the above controls, actually it’s nearly identical to the pagePicker only you have to specify the AppAlias and TreeAlias to load for the picker.
    • It’s been upgraded to inherit from BaseTreePicker also

So basically, everything will look pretty much the same, but will be a lot faster and MUCH easier to develop with if you’re creating custom packages or whatever. It’s all backwards compatible (apart from the JavaScripting) but under the hood is much different.

So now at least when you load up the TinyMCE insert image dialog, you end up with 1 frame (the modal dialog) instead of 4!

Oh yeah, and this hasn’t been checked in to the 4.1 branch as of today… perhaps next week!

Categories: .Net | Umbraco