Organization Re-Structure Control using jQuery.nestable

Hi Guys,

This is a continuation of my previous post. I made a control which allows the user to restructure the organization and save it in the database. Hope you like it.

Initial View of the Organization Structure.


The user can drag and drop to restructure the organization structure and click "Save Changes" to save it. 



How it looks in Organization Chart Control. After saving the changes.

















1) OrgnizationOrderControl.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.Web.UI.WebControls;
using System.IO;
using System.Text;

/// <summary>
/// Summary description for OrganizationOrderControl
/// </summary>
public class OrganizationOrderControl<T> : Control
{
    private List<TreeNode> Nodes { get; set; }

    public OrganizationOrderControl()
    { }

    public List<T> Items
    {
        get
        {
            if (ViewState["Items"] == null)
                ViewState["Items"] = new List<object>();
            return (List<T>)ViewState["Items"];
        }
        set { ViewState["Items"] = value; }
    }

    public string ParentColumnID
    {
        get
        {

            return (string)ViewState["ParentColumnID"];
        }
        set { ViewState["ParentColumnID"] = value; }
    }

    public string ValueMember
    {
        get
        {

            return (string)ViewState["ValueMember"];
        }
        set { ViewState["ValueMember"] = value; }
    }

    public string DisplayMember
    {
        get
        {

            return (string)ViewState["DisplayMember"];
        }
        set { ViewState["DisplayMember"] = value; }
    }

    public int TrimCharacter
    {
        get
        {

            return (int)ViewState["TrimCharacter"];
        }
        set { ViewState["TrimCharacter"] = value; }
    }

    private Type type;
    private PropertyInfo parentColumn, valueColumn, displayColumn;
    int p, v, i = 1;
    string text, url;

    /*<ol class="dd-list">
                <li class="dd-item dd3-item" data-id="13">
                    <div class="dd-handle dd3-handle">Drag</div><div class="dd-handle">Item 13</div>
                </li>
                <li class="dd-item dd3-item" data-id="14">
                    <div class="dd-handle dd3-handle">Drag</div><div class="dd-handle">Item 14</div>
                </li>
                <li class="dd-item dd3-item" data-id="15">
                    <div class="dd-handle dd3-handle">Drag</div><div class="dd-handle">Item 15</div>
                    <ol class="dd-list">
                        <li class="dd-item dd3-item" data-id="16">
                            <div class="dd-handle dd3-handle">Drag</div><div class="dd-handle">Item 16</div>
                        </li>
                        <li class="dd-item dd3-item" data-id="17">
                            <div class="dd-handle dd3-handle">Drag</div><div class="dd-handle">Item 17</div>
                        </li>
                        <li class="dd-item dd3-item" data-id="18">
                            <div class="dd-handle dd3-handle">Drag</div><div class="dd3-content">Item 18</div>
                        </li>
                    </ol>
                </li>
            </ol>*/

    protected override void Render(HtmlTextWriter writer)
    {
        if (this.Nodes == null)
            this.Nodes = new List<TreeNode>();

        url = HttpContext.Current.Request.Url.AbsoluteUri;
        if (url.Contains("?"))
            url = url.Substring(0, url.IndexOf("?"));

        try
        {
            if (Items.Count > 0)
            {
                if (Items.Count > 1)
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-list");
                writer.RenderBeginTag(HtmlTextWriterTag.Ol);

                foreach (T listItem in Items)
                {
                    type = typeof(T);

                    parentColumn = type.GetProperties().Where(r => r.Name == ParentColumnID).FirstOrDefault();

                    valueColumn = type.GetProperties().Where(r => r.Name == ValueMember).FirstOrDefault();

                    displayColumn = type.GetProperties().Where(r => r.Name == DisplayMember).FirstOrDefault();

                    CreateNode(Convert.ToInt32(valueColumn.GetValue(listItem)),
                        Convert.ToInt32(parentColumn.GetValue(listItem)),
                        displayColumn.GetValue(listItem).ToString(),
                        writer);



                }
                if (Items.Count > 1)
                    writer.RenderEndTag();



                writer.RenderEndTag();

                if (HttpContext.Current.Request.Url.AbsoluteUri.Contains("?"))
                {
                    if (Items.Count > 1)
                        writer.RenderEndTag();
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "spanBack");
                    writer.RenderBeginTag(HtmlTextWriterTag.Span);
                    writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:window.history.back();");

                    writer.RenderBeginTag(HtmlTextWriterTag.A);
                    writer.Write("Back");
                    writer.RenderEndTag();
                    writer.RenderEndTag();
                }

            }
        }
        catch (Exception ex)
        {


        }

        base.Render(writer);


    }

    private void CreateNode(int id, int parentID, string text, HtmlTextWriter writer)
    {
        if (parentID == -1)
        {
            TreeNode _Node = new TreeNode(text, id.ToString());
            this.Nodes.Add(_Node);
            writer.AddAttribute("data-id", id.ToString());
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-item dd3-item");
            writer.RenderBeginTag(HtmlTextWriterTag.Li);
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-handle dd3-handle");
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.Write(id);
            writer.RenderEndTag();
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd3-content");
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.Write(text);

            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Ol);
        }



        if (parentID != -1)
        {

            foreach (TreeNode _Node in this.Nodes)
            {
                if (_Node.Value == parentID.ToString())
                {
                    writer.AddAttribute("data-id", id.ToString());
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-item dd3-item");
                    writer.RenderBeginTag(HtmlTextWriterTag.Li);
                    writer.AddAttribute(HtmlTextWriterAttribute.Href, url + "?pId=" + id);
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-handle dd3-handle");
                    writer.RenderBeginTag(HtmlTextWriterTag.Div);
                    writer.Write(id);
                    writer.RenderEndTag();
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd3-content");
                    writer.RenderBeginTag(HtmlTextWriterTag.Div);
                    writer.Write(text);
                    writer.RenderEndTag();

                    TreeNode childNode = new TreeNode(text, id.ToString());
                    //_Node.ChildNodes.Add(childNode);
                    GetChildNodes(id, parentID, childNode, writer);



                    writer.RenderEndTag();


                }
            }

        }

    }

    private void GetChildNodes(int id, int parentID, TreeNode node, HtmlTextWriter writer)
    {


        try
        {

            var filter = Filter<T>(Items, ParentColumnID, id.ToString());
            if (filter.Count > 0)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Ol);
                foreach (T listItem in filter)
                {
                    type = typeof(T);

                    //if (i == 10) break;

                    parentColumn = type.GetProperties().Where(r => r.Name == ParentColumnID).FirstOrDefault();

                    valueColumn = type.GetProperties().Where(r => r.Name == ValueMember).FirstOrDefault();

                    displayColumn = type.GetProperties().Where(r => r.Name == DisplayMember).FirstOrDefault();

                    v = Convert.ToInt32(valueColumn.GetValue(listItem));


                    p = Convert.ToInt32(parentColumn.GetValue(listItem));

                    text = displayColumn.GetValue(listItem).ToString();
                    writer.AddAttribute("data-id", v.ToString());
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-item dd3-item");
                    writer.RenderBeginTag(HtmlTextWriterTag.Li);
                    //writer.AddAttribute(HtmlTextWriterAttribute.Href, url + "?pId=" + id);
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd-handle dd3-handle");
                    writer.RenderBeginTag(HtmlTextWriterTag.Div);
                    writer.Write(id);
                    writer.RenderEndTag();
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "dd3-content");
                    writer.RenderBeginTag(HtmlTextWriterTag.Div);
                    writer.Write(text);

                    writer.RenderEndTag();
                    //writer.RenderBeginTag(HtmlTextWriterTag.Ol);
                    TreeNode child = new TreeNode(v.ToString(), p.ToString());

                    node.ChildNodes.Add(child);
                    GetChildNodes(v, id, child, writer);

                    //writer.RenderEndTag();
                    writer.RenderEndTag();



                }
                writer.RenderEndTag();

            }


        }
        catch (Exception ex)
        {


        }


    }

    private List<T> Filter<T>(
    List<T> collection,
    string property,
    string filterValue)
    {
        var filteredCollection = new List<T>();
        foreach (var item in collection)
        {
            // To check multiple properties use,
            // item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)

            var propertyInfo =
                item.GetType()
                    .GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
            if (propertyInfo == null)
                throw new NotSupportedException("property given does not exists");

            string propertyValue = propertyInfo.GetValue(item, null).ToString();
            if (propertyValue == filterValue)
                filteredCollection.Add(item);
        }

        return filteredCollection;
    }

   
}


2) OrganizationOrderControl.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="OrganizationOrderControl.ascx.cs" Inherits="OrganizationChart_OrganizationOrderControl" %>
<script src="../Scripts/jquery.nestable.js"></script>


<style type="text/css">
    .tree ol {
padding-top: 20px; position: relative;

transition: all 0.5s;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
}

.tree li {
float: left; text-align: center;
list-style-type: none;
position: relative;
padding: 20px 1px 0 1px;

transition: all 0.5s;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
}

/*We will use ::before and ::after to draw the connectors*/

.tree li::before, .tree li::after{
content: '';
position: absolute; top: 0; right: 50%;
border-top: 1px solid #ccc;
width: 50%; height: 20px;
}
.tree li::after{
right: auto; left: 50%;
border-left: 1px solid #ccc;
}

/*We need to remove left-right connectors from elements without 
any siblings*/
.tree li:only-child::after, .tree li:only-child::before {
display: none;
}

/*Remove space from the top of single children*/
.tree li:only-child{ padding-top: 0;}

/*Remove left connector from first child and 
right connector from last child*/
.tree li:first-child::before, .tree li:last-child::after{
border: 0 none;
}
/*Adding back the vertical connector to the last nodes*/
.tree li:last-child::before{
border-right: 1px solid #ccc;
border-radius: 0 5px 0 0;
-webkit-border-radius: 0 5px 0 0;
-moz-border-radius: 0 5px 0 0;
}
.tree li:first-child::after{
border-radius: 5px 0 0 0;
-webkit-border-radius: 5px 0 0 0;
-moz-border-radius: 5px 0 0 0;
}

/*Time to add downward connectors from parents*/
.tree ol ol::before{
content: '';
position: absolute; top: 0; left: 50%;
border-left: 1px solid #ccc;
width: 0; height: 20px;
}


.cf:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; }
* html .cf { zoom: 1; }
*:first-child+html .cf { zoom: 1; }

html { margin: 0; padding: 0; }
body { font-size: 100%; margin: 0; padding: 1.75em; font-family: 'Helvetica Neue', Arial, sans-serif; }

h1 { font-size: 1.75em; margin: 0 0 0.6em 0; }

a { color: #2996cc; }
a:hover { text-decoration: none; }

p { line-height: 1.5em; }
.small { color: #666; font-size: 0.875em; }
.large { font-size: 1.25em; }

/**
 * Nestable
 */

.dd { position: relative; display: block; margin: 0; padding: 0; max-width: 600px; list-style: none; font-size: 13px; line-height: 20px; }

.dd-list { display: block; position: relative; margin: 0; padding: 0; list-style: none; }
.dd-list .dd-list { padding-left: 30px; }
.dd-collapsed .dd-list { display: none; }

.dd-item,
.dd-empty,
.dd-placeholder { display: block; position: relative; margin: 0; padding: 0; min-height: 20px; font-size: 13px; line-height: 20px; }

.dd-handle { display: block; height: 30px; margin: 5px 0; padding: 5px 10px; color: #333; text-decoration: none; font-weight: bold; border: 1px solid #ccc;
    background: #fafafa;
    background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
    background:    -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
    background:         linear-gradient(top, #fafafa 0%, #eee 100%);
    -webkit-border-radius: 3px;
            border-radius: 3px;
    box-sizing: border-box; -moz-box-sizing: border-box;
}
.dd-handle:hover { color: #2ea8e5; background: #fff; }

.dd-item > button { display: block; position: relative; cursor: pointer; float: left; width: 25px; height: 20px; margin: 5px 0; padding: 0; text-indent: 100%; white-space: nowrap; overflow: hidden; border: 0; background: transparent; font-size: 12px; line-height: 1; text-align: center; font-weight: bold; }
.dd-item > button:before { content: '+'; display: block; position: absolute; width: 100%; text-align: center; text-indent: 0; }
.dd-item > button[data-action="collapse"]:before { content: '-'; }

.dd-placeholder,
.dd-empty { margin: 5px 0; padding: 0; min-height: 30px; background: #f2fbff; border: 1px dashed #b6bcbf; box-sizing: border-box; -moz-box-sizing: border-box; }
.dd-empty { border: 1px dashed #bbb; min-height: 100px; background-color: #e5e5e5;
    background-image: -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),
                      -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
    background-image:    -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),
                         -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
    background-image:         linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),
                              linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
    background-size: 60px 60px;
    background-position: 0 0, 30px 30px;
}

.dd-dragel { position: absolute; pointer-events: none; z-index: 9999; }
.dd-dragel > .dd-item .dd-handle { margin-top: 0; }
.dd-dragel .dd-handle {
    -webkit-box-shadow: 2px 4px 6px 0 rgba(0,0,0,.1);
            box-shadow: 2px 4px 6px 0 rgba(0,0,0,.1);
}

/**
 * Nestable Extras
 */

.nestable-lists { display: block; clear: both; padding: 30px 0; width: 100%; border: 0; border-top: 2px solid #ddd; border-bottom: 2px solid #ddd; }

#nestable-menu { padding: 0; margin: 20px 0; }

#nestable-output,
#nestable2-output { width: 100%; height: 7em; font-size: 0.75em; line-height: 1.333333em; font-family: Consolas, monospace; padding: 5px; box-sizing: border-box; -moz-box-sizing: border-box; }

#nestable2 .dd-handle {
    color: #fff;
    border: 1px solid #999;
    background: #bbb;
    background: -webkit-linear-gradient(top, #bbb 0%, #999 100%);
    background:    -moz-linear-gradient(top, #bbb 0%, #999 100%);
    background:         linear-gradient(top, #bbb 0%, #999 100%);
}
#nestable2 .dd-handle:hover { background: #bbb; }
#nestable2 .dd-item > button:before { color: #fff; }

@media only screen and (min-width: 700px) {

    .dd { float: left; width: 48%; }
    .dd + .dd { margin-left: 2%; }

}

.dd-hover > .dd-handle { background: #2ea8e5 !important; }

/**
 * Nestable Draggable Handles
 */

.dd3-content { display: block; height: 30px; margin: 5px 0; padding: 5px 10px 5px 40px; color: #333; text-decoration: none; font-weight: bold; border: 1px solid #ccc;
    background: #fafafa;
    background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
    background:    -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
    background:         linear-gradient(top, #fafafa 0%, #eee 100%);
    -webkit-border-radius: 3px;
            border-radius: 3px;
    box-sizing: border-box; -moz-box-sizing: border-box;
}
.dd3-content:hover { color: #2ea8e5; background: #fff; }

.dd-dragel > .dd3-item > .dd3-content { margin: 0; }

.dd3-item > button { margin-left: 30px; }

.dd3-handle { position: absolute; margin: 0; left: 0; top: 0; cursor: pointer; width: 30px; text-indent: 100%; white-space: nowrap; overflow: hidden;
    border: 1px solid #aaa;
    background: #ddd;
    background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
    background:    -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
    background:         linear-gradient(top, #ddd 0%, #bbb 100%);
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.dd3-handle:before { content: '≡'; display: block; position: absolute; left: 0; top: 3px; width: 100%; text-align: center; text-indent: 0; color: #fff; font-size: 20px; font-weight: normal; }
.dd3-handle:hover { background: #ddd; }

    </style>
<script >

    

    $(document).ready(
        function()
        {
            //$('#nestable3').nestable();
            var updateOutput = function (e) {
                var list = e.length ? e : $(e.target),
                    output = list.data('output');
               
                if (window.JSON) {
                    output.val(window.JSON.stringify(list.nestable('serialize')));//, null, 2));
                } else {
                    output.val('JSON browser support required for this demo.');
                }
            };

            // activate Nestable for list 1
            $('#OrganizationChartControl_nestable3').nestable({
                group: 1
            })
            .on('change', updateOutput);
           
            updateOutput($( <%= string.Format("'#{0}'",nestable3.ClientID) %>).data('output', $(<%= string.Format("'#{0}'",nestableoutput.ClientID) %>)));
        }
        );

    function postData()
    {
        var list = $(<%= string.Format("'#{0}'",nestable3.ClientID) %>).data('output', $(<%= string.Format("'#{0}'",nestableoutput.ClientID) %>));
        $.post('/Services/LocationService.svc/SaveLog',
            window.JSON.stringify(list.nestable('serialize')),
            function (d, s, jq) { alert('Success'); });
    }
</script>
 <input type="hidden" id="nestableoutput" runat="server"></input>
 <div class="cf nestable-lists">

    

        <div class="dd" id="nestable3" runat="server">
            
        </div>

    </div>


<asp:Button ID="btnSave" OnClientClick="return confirm('Are you sure?');" runat="server" OnClick="btnClick_Click" Text="Save Changes" />

   3) OrganizationOrderControl.ascx.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class OrganizationChart_OrganizationOrderControl : System.Web.UI.UserControl
{
    int parentId = -1;
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            BindData();
        }
    }

    private void BindData()
    {
       


        if (Request.QueryString["pID"] != null)
        {
            parentId = int.Parse(Request.QueryString["pID"]);
        }

     

        OrganizationOrderControl<OrginationStructure> listcontrol = new OrganizationOrderControl<OrginationStructure>();
        listcontrol.Items = parentId == -1 ? LocationDataHelper.GetAllDataAsList() : LocationDataHelper.GetDataAsList(parentId);
        listcontrol.ValueMember = "ID";
        listcontrol.DisplayMember = "Name";
        listcontrol.ParentColumnID = "ParentID";


        nestable3.Controls.Add(listcontrol);
    }
    protected void btnClick_Click(object sender, EventArgs e)
    {
        List<Child> c = Deserialize<List<Child>>(nestableoutput.Value);
        LocationDataHelper.SaveOrginationStructure(c);
        BindData();
    }

    public static T Deserialize<T>(string json)
    {
        T obj = Activator.CreateInstance<T>();
        MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
        obj = (T)serializer.ReadObject(ms);
        ms.Close();
        return obj;
    }

}

    
4) Recursive Method to Save changes passed as JSON and Child Class

public class Child
{
    public int id { get; set; }
    public Child[] children { get; set; }  

}

 public static bool SaveOrginationStructure(List<Child> lstChild)
    {
        bool bool2Return = false;

        List<OrginationStructure> lstStructure = new List<OrginationStructure>();
        foreach (var child in lstChild)
        {
            SavingChildren(child);
        }
        bool2Return =true;
        return bool2Return;

    }

    private static void SavingChildren(Child child)
    {
        if (child.children == null || (child.children != null && child.children.Count() == 0))
            return;
        else
        {
            using (LocationDataDataContext context = new LocationDataDataContext())
            {
                foreach(Child objChild in child.children)
                {

                    var objStructure = (from r in context.OrginationStructures
                                        where r.ID == objChild.id
                                select r).SingleOrDefault();

                    if (objStructure != null)
                    {
                        objStructure.ParentID = child.id;
                        objStructure.UpdateByUser = "sshahim";
                        objStructure.UpdateDatetime = DateTime.Now;
                        //context.OrginationStructures.Attach(objStructure);
                        context.SubmitChanges();
                    }

                    SavingChildren(objChild);
                }           

            }
        }

    }

5) Data

Before Changes 










After Changes 







Comments