Tuesday, 12 January 2016

Create Custom Organization Chart Using REST in SharePoint 2013

In this post, I have written the code to create the custom org chart using REST API in sharepoint 2013.

I  have used  "easyTree.js" file that builds  the tree  architecture allows expand and collapse functionality.

Below is the code of "EasyTree.js"
===================================================================


(function ($) {
    $.fn.EasyTree = function (options) {
        var defaults = {
            selectable: true,
            deletable: false,
            editable: false,
            addable: false,
            i18n: {
                deleteNull: 'Select a node to delete',
                deleteConfirmation: 'Delete this node?',
                confirmButtonLabel: 'Okay',
                editNull: 'Select a node to edit',
                editMultiple: 'Only one node can be edited at one time',
                addMultiple: 'Select a node to add a new node',
                collapseTip: 'collapse',
                expandTip: 'expand',
                selectTip: 'select',
                unselectTip: 'unselet',
                editTip: 'edit',
                addTip: 'add',
                deleteTip: 'delete',
                cancelButtonLabel: 'cancle'
            }
        };

        var warningAlert = $('<div class="alert alert-warning alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button><strong></strong><span class="alert-content"></span> </div> ');
        var dangerAlert = $('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button><strong></strong><span class="alert-content"></span> </div> ');

        var createInput = $('<div class="input-group"><input type="text" class="form-control"><span class="input-group-btn"><button type="button" class="btn btn-default btn-success confirm"></button> </span><span class="input-group-btn"><button type="button" class="btn btn-default cancel"></button> </span> </div> ');

        options = $.extend(defaults, options);

        this.each(function () {
            var easyTree = $(this);
            $.each($(easyTree).find('ul > li'), function() {
                var text;
                if($(this).is('li:has(ul)')) {
                    var children = $(this).find(' > ul');
                    $(children).remove();
                    text = $(this).html();
                    if(!$(this).find(' > span > span').hasClass("glyphicon"))
                    {
                    $(this).html('<span><span class="glyphicon"></span><a href="javascript: void(0);"></a> </span>');
                    $(this).find(' > span > span').addClass('glyphicon-folder-open');
                    $(this).find(' > span > a').html(text);

                    $(this).append(children);
                    }
                }
                else {
                    text = $(this).html();
                    if(!$(this).find(' > span > span').hasClass("glyphicon"))
                    {
                    $(this).html('<span><span class="glyphicon"></span><a href="javascript: void(0);"></a> </span>');
                    $(this).find(' > span > span').addClass('glyphicon-file');
                    $(this).find(' > span > a').html(text);
                    }
                }
            });

            $(easyTree).find('li:has(ul)').addClass('parent_li').find(' > span').attr('title', options.i18n.collapseTip);
            

            // add easy tree toolbar dom
            if (options.deletable || options.editable || options.addable) {
                $(easyTree).prepend('<div class="easy-tree-toolbar"></div> ');
            }

            // addable
            if (options.addable) {
                $(easyTree).find('.easy-tree-toolbar').append('<div class="create"><button class="btn btn-default btn-sm btn-success"><span class="glyphicon glyphicon-plus"></span></button></div> ');
                $(easyTree).find('.easy-tree-toolbar .create > button').attr('title', options.i18n.addTip).click(function () {
                    var createBlock = $(easyTree).find('.easy-tree-toolbar .create');
                    $(createBlock).append(createInput);
                    $(createInput).find('input').focus();
                    $(createInput).find('.confirm').text(options.i18n.confirmButtonLabel);
                    $(createInput).find('.confirm').click(function () {
                        if ($(createInput).find('input').val() === '')
                            return;
                        var selected = getSelectedItems();
                        var item = $('<li><span><span class="glyphicon glyphicon-file"></span><a href="javascript: void(0);">' + $(createInput).find('input').val() + '</a> </span></li>');
                        $(item).find(' > span > span').attr('title', options.i18n.collapseTip);
                        $(item).find(' > span > a').attr('title', options.i18n.selectTip);
                        if (selected.length <= 0) {
                            $(easyTree).find(' > ul').append($(item));
                        } else if (selected.length > 1) {
                            $(easyTree).prepend(warningAlert);
                            $(easyTree).find('.alert .alert-content').text(options.i18n.addMultiple);
                        } else {
                            if ($(selected).hasClass('parent_li')) {
                                $(selected).find(' > ul').append(item);
                            } else {
                                $(selected).addClass('parent_li').find(' > span > span').addClass('glyphicon-folder-open').removeClass('glyphicon-file');
                                $(selected).append($('<ul></ul>')).find(' > ul').append(item);
                            }
                        }
                        $(createInput).find('input').val('');
                        if (options.selectable) {
                            $(item).find(' > span > a').attr('title', options.i18n.selectTip);
                            $(item).find(' > span > a').click(function (e) {
                                
                                var li = $(this).parent().parent();
                                if (li.hasClass('li_selected')) {
                                    $(this).attr('title', options.i18n.selectTip);
                                    $(li).removeClass('li_selected');
                                }
                                else {
                                    $(easyTree).find('li.li_selected').removeClass('li_selected');
                                    $(this).attr('title', options.i18n.unselectTip);
                                    $(li).addClass('li_selected');
                                }

                                if (options.deletable || options.editable || options.addable) {
                                    var selected = getSelectedItems();
                                    if (options.editable) {
                                        if (selected.length <= 0 || selected.length > 1)
                                            $(easyTree).find('.easy-tree-toolbar .edit > button').addClass('disabled');
                                        else
                                            $(easyTree).find('.easy-tree-toolbar .edit > button').removeClass('disabled');
                                    }

                                    if (options.deletable) {
                                        if (selected.length <= 0 || selected.length > 1)
                                            $(easyTree).find('.easy-tree-toolbar .remove > button').addClass('disabled');
                                        else
                                            $(easyTree).find('.easy-tree-toolbar .remove > button').removeClass('disabled');
                                    }

                                }

                                e.stopPropagation();

                            });
                        }
                        $(createInput).remove();
                    });
                    $(createInput).find('.cancel').text(options.i18n.cancelButtonLabel);
                    $(createInput).find('.cancel').click(function () {
                        $(createInput).remove();
                    });
                });
            }

            // editable
            if (options.editable) {
                $(easyTree).find('.easy-tree-toolbar').append('<div class="edit"><button class="btn btn-default btn-sm btn-primary disabled"><span class="glyphicon glyphicon-edit"></span></button></div> ');
                $(easyTree).find('.easy-tree-toolbar .edit > button').attr('title', options.i18n.editTip).click(function () {
                    $(easyTree).find('input.easy-tree-editor').remove();
                    $(easyTree).find('li > span > a:hidden').show();
                    var selected = getSelectedItems();
                    if (selected.length <= 0) {
                        $(easyTree).prepend(warningAlert);
                        $(easyTree).find('.alert .alert-content').html(options.i18n.editNull);
                    }
                    else if (selected.length > 1) {
                        $(easyTree).prepend(warningAlert);
                        $(easyTree).find('.alert .alert-content').html(options.i18n.editMultiple);
                    }
                    else {
                        var value = $(selected).find(' > span > a').text();
                        $(selected).find(' > span > a').hide();
                        $(selected).find(' > span').append('<input type="text" class="easy-tree-editor">');
                        var editor = $(selected).find(' > span > input.easy-tree-editor');
                        $(editor).val(value);
                        $(editor).focus();
                        $(editor).keydown(function (e) {
                            if (e.which == 13) {
                                if ($(editor).val() !== '') {
                                    $(selected).find(' > span > a').text($(editor).val());
                                    $(editor).remove();
                                    $(selected).find(' > span > a').show();
                                }
                            }
                        });
                    }
                });
            }

            // deletable
            if (options.deletable) {
                $(easyTree).find('.easy-tree-toolbar').append('<div class="remove"><button class="btn btn-default btn-sm btn-danger disabled"><span class="glyphicon glyphicon-remove"></span></button></div> ');
                $(easyTree).find('.easy-tree-toolbar .remove > button').attr('title', options.i18n.deleteTip).click(function () {
                    var selected = getSelectedItems();
                    if (selected.length <= 0) {
                        $(easyTree).prepend(warningAlert);
                        $(easyTree).find('.alert .alert-content').html(options.i18n.deleteNull);
                    } else {
                        $(easyTree).prepend(dangerAlert);
                        $(easyTree).find('.alert .alert-content').html(options.i18n.deleteConfirmation)
                            .append('<a style="margin-left: 10px;" class="btn btn-default btn-danger confirm"></a>')
                            .find('.confirm').html(options.i18n.confirmButtonLabel);
                        $(easyTree).find('.alert .alert-content .confirm').on('click', function () {
                            $(selected).find(' ul ').remove();
                            if($(selected).parent('ul').find(' > li').length <= 1) {
                                $(selected).parents('li').removeClass('parent_li').find(' > span > span').removeClass('glyphicon-folder-open').addClass('glyphicon-file');
                                $(selected).parent('ul').remove();
                            }
                            $(selected).remove();
                            $(dangerAlert).remove();
                        });
                    }
                });
            }


            // collapse or expand
            $(easyTree).delegate('li.parent_li > span', 'click', function (e) {
            
                var children = $(this).parent('li.parent_li').find(' > ul > li');
                if (children.is(':visible')) {
                    children.hide('fast');
                    
                    $(this).attr('title', options.i18n.expandTip)
                        .find(' > span.glyphicon')
                        .addClass('glyphicon-folder-close')
                        .removeClass('glyphicon-folder-open');
                } else {
                    children.show('fast');
                    $(this).attr('title', options.i18n.collapseTip)
                        .find(' > span.glyphicon')
                        .addClass('glyphicon-folder-open')
                        .removeClass('glyphicon-folder-close');
                }
                e.stopPropagation();
            });

            // selectable, only single select
            if (options.selectable) {
                $(easyTree).find('li > span > a').attr('title', options.i18n.selectTip);
                $(easyTree).find('li > span > a').click(function (e) {
                   
// var htmlTest = "<ul><li><img src='images/profile-icon-lrg.png'>Smith<br>Staff</li><li><img src='images/profile-icon-lrg.png'>LTaylor<br>Staff</li></ul>";
                    
                    var li = $(this).parent().parent();
                    
                    var userEmail=li.find('p').text();
                    LoadFurtherUserData(li,userEmail);
                   // alert(li.find('p').text());
                    
                   // li.append(htmlTest);
                    if (li.hasClass('li_selected')) {
                        $(this).attr('title', options.i18n.selectTip);
                        $(li).removeClass('li_selected');
                    }
                    else {
                        $(easyTree).find('li.li_selected').removeClass('li_selected');
                        $(this).attr('title', options.i18n.unselectTip);
                        $(li).addClass('li_selected');
                    }

                    if (options.deletable || options.editable || options.addable) {
                        var selected = getSelectedItems();
                        if (options.editable) {
                            if (selected.length <= 0 || selected.length > 1)
                                $(easyTree).find('.easy-tree-toolbar .edit > button').addClass('disabled');
                            else
                                $(easyTree).find('.easy-tree-toolbar .edit > button').removeClass('disabled');
                        }

                        if (options.deletable) {
                            if (selected.length <= 0 || selected.length > 1)
                                $(easyTree).find('.easy-tree-toolbar .remove > button').addClass('disabled');
                            else
                                $(easyTree).find('.easy-tree-toolbar .remove > button').removeClass('disabled');
                        }

                    }

                    e.stopPropagation();

                });
            }

            // Get selected items
            var getSelectedItems = function () {
                return $(easyTree).find('li.li_selected');
            };
        });
    };
    
   
    
})(jQuery);



Below is the code of "OrgChart.js"
==========================================================


I  have used a  custom "orgchart.js" which used REST API to search the user profile details for the people.

=========================================================
var _userProfileData;
var userDetails= [];
var jobTitle='';
var displayName=''
 var picPath="https://{siteURL}/sites/intranet/_layouts/15/userphoto.aspx?size=S&accountname=";
 var defaultImage="https:// {siteURL}/sites/tatesit/Style%20Library/Images/profile-icon-lrg.png";
function UserProfileDataLoader(data,serviceName) {


 

    var firstName=data.split('@')[0];
     var emailPart=data.split('@')[1];

    var requestHeaders = { "accept": "application/json; odata=verbose" };
    $.ajax({
        url: "https:// {siteURL}/sites/intranet/_api/sp.userprofiles.peoplemanager/getpropertiesfor%28@v%29?@v=%27i%3A0%23.f%7Cmembership%7C"+firstName+"%40"+emailPart+"%27",
        contentType: "application/json;odata=verbose",
        method: "GET",
        headers: requestHeaders,
        success:  function (data)
                {
                                                                _userProfileData.orgData = data;
                                                                if(serviceName=="SpecificEmailPersonalDetail")
                                                                {
                                                                _userProfileData.jobTitle=data.d.Title;
                                                                _userProfileData.displayName=data.d.DisplayName;
                                                               
                                                                }
                                                                },
        async: false,
        error: function (err) {
            alert("error: " + JSON.stringify(err));
        }

    });
}

function RedirectToMyProfile(email)
{
var firstName=email.split('@')[0];
 var emailPart=email.split('@')[1];
var url="https://{tenancyName}-my.sharepoint.com/PersonImmersive.aspx?accountname=i%3A0%23.f%7Cmembership%7C"+firstName+"%40"+emailPart+"";
var win = window.open(url, '_blank');
  win.focus()
   //alert(window.open(path));
}

function LoadSpecificUserDetails(email)
{
   UserProfileDataLoader(email,"SpecificEmailPersonalDetail");
}

function RenderMainUserData() {
  
  var response= _userProfileData.orgData; //Main Data results for the Department head
   
    var email=response.d.Email;
    var displayName=response.d.DisplayName;
    var JobTitle=response.d.Title;
    var department=response.d.UserProfileProperties.results[11].Value;   
    var htmlContent = '<ul>';
  
    var picName='';
    
   //step1 Read the Manager info of the User
                 $.each(response.d.ExtendedManagers.results, function (i)
                  {
      email=response.d.ExtendedManagers.results[i].split('|')[2]
      LoadSpecificUserDetails(email);
     // picName=picPath+email.replace('.','_').replace('@','_').replace('.','_')+"_MThumb.jpg";           
      picName=picPath+email;           
      htmlContent+='<li><img onclick=javascript:RedirectToMyProfile("'+email+'") src="'+picName+'" alt="julia" />'+_userProfileData.displayName+'<br>'+_userProfileData.jobTitle+'';
      });
   
    //step2 Read the unser details for which ORG chart is displayed
     //picName=picPath+response.d.Email.replace('.','_').replace('@','_').replace('.','_')+"_MThumb.jpg";
     picName=picPath+response.d.Email;
     htmlContent+='<ul><li class="li_selected"><img onclick=javascript:RedirectToMyProfile("'+response.d.Email+'") src="'+picName+'">'+displayName+'<br>'+JobTitle+'';
    
                //step3  Read the Users who directly reports to the specified User
                 htmlContent+='<ul>'
                $.each(response.d.DirectReports.results, function (i)
                 {
      email=response.d.DirectReports.results[i].split('|')[2]
      LoadSpecificUserDetails(email);
    //  picName=picPath+email.replace('.','_').replace('@','_').replace('.','_')+"_MThumb.jpg";
      picName=picPath+email;
      htmlContent+='<li><img onclick=javascript:RedirectToMyProfile("'+email+'") src="'+picName+'">'+_userProfileData.displayName+'<br>'+_userProfileData.jobTitle+' <br><p class="hide">'+email+'</></li>';
     });
    htmlContent+='</ul></li></ul>';
   
    //Step 4 Read Extended Reporters
   // $.each(response.d.ExtendedReports.results, function (i) {
     
    //  email=response.d.ExtendedReports.results[i].split('|')[2]
                // LoadSpecifiUserDetails(email);

      //htmlContent+='<div>'+_userProfileData.displayName+'</div><div class=ExtendedReporters>'+_userProfileData.jobTitle+'</div>';//Reads the users who reports to intended user. i.e Rebecca
     

    //});
   
    htmlContent+='</li></ul>';
    $(".easy-tree").html(htmlContent);
    _userProfileData.orgData='';   
               
}

var UserProfileData= function () {
    this.orgData = '';  
    this.userSpecificDetails =[];
    this.jobTitle='';
    this.displayName='';
}

 function LoadFurtherUserData(selectedLi, userEmail)
    {
   var count = (userEmail.match(/@/g) || []).length;
    if(count==1)
    {
      UserProfileDataLoader(userEmail,"main")
    var response= _userProfileData.orgData;
  
    var htmlFurthereUsers="<ul>";
    var itemCount=0;
                $.each(response.d.DirectReports.results, function (i)
                 {
                 itemCount=1;
      email=response.d.DirectReports.results[i].split('|')[2]
      LoadSpecificUserDetails(email);
      picName=picPath+email;
      htmlFurthereUsers+='<li><img onclick=javascript:RedirectToMyProfile("'+email+'") src="'+picName+'">'+_userProfileData.displayName+'<br>'+_userProfileData.jobTitle+' <br><p class="hide">'+email+'</></li>';
     });
     htmlFurthereUsers+="</ul>";
    
     if(itemCount==1)
     {
     selectedLi.append(htmlFurthereUsers);
     _userProfileData.orgData='';
      selectedLi.EasyTree({
                addable: false,
                editable: false,
                deletable: false
            });
           
           selectedLi.addClass('parent_li')     
          
           }
          
      }
  //  alert(userEmail);
    }
$(document).ready(function ()
{
                _userProfileData= new UserProfileData();
                 if(sessionStorage.getItem('DataMain')==null)
                 {
                 UserProfileDataLoader("rebecca.watson@domain.com","main");
                 sessionStorage.setItem('DataMain',  JSON.stringify(_userProfileData.orgData));
                 }
                 else
                 {
                 
                 
                    _userProfileData.orgData= JSON.parse(sessionStorage.getItem('DataMain'));
                 }
                 RenderMainUserData();
                //console.log(_userProfileData.orgData);
                 $('.easy-tree').EasyTree({
                addable: true,
                editable: true,
                deletable: true
            });
           
  });
========================================================

HTML  will contain a div with class name "easy-tree"

like <div class="easy-div"></div>


CSS  file:
===========================================================
/** Easy Tree style */
.glyphicon-folder-open::before {
   /* content: "‹";*/
   content:url(images/down.png);
  
    left: 63px;
    margin: 5px;
    position: absolute;
    top: 47px;
    width: 2%;}
.glyphicon-folder-close::before, .glyphicon-file::before {
   /* content: "›";*/
   content:url(images/up.png);
   
    left: 63px;
    margin: 5px;
    position: absolute;
    top: 47px;
    width: 2%;
   }
.ms-WPBorder {
    border: 0 solid #c6c6c6;
}
.hide{
display:none;
}
.easy-tree {
    min-height: 20px;
    margin-bottom: 20px;
   
    color: #fff;
    border: none;
    border-top: none;
    padding-bottom: 15px;
}

.easy-tree ul {
    margin-left: 2px;
    margin-top: -3px;
    padding: 0 0 0 30px !important;
}
.easy-tree li {
    list-style-type: none;
    margin: 0;
    padding: 10px 5px 0 5px;
    position: relative
}

.easy-tree li::before, .easy-tree li::after {
    content: '';
    left: -32px;
    position: absolute;
    right: auto
}

.easy-tree li::before {
    border-left: 1px solid #01b6b1;
    bottom: 50px;
    height: 100%;
    top: 0;
    width: 1px
}

.easy-tree li::after {
    border-top: 1px solid #01b6b1;
    height: 20px;
    top: 41px;
    width: 47px;
}
.ms-WPBorder {
   
    overflow: visible !important;
}
.easy-tree li > div{display:block!important;}
.easy-tree li > span {
    display: inline-block;
    padding: 5px;
    text-decoration: none;
    width: 100%;
}

.easy-tree li.parent_li > span {
    cursor: pointer
}


.easy-tree > ul > li::before, .easy-tree > ul > li::after {
    border: 0
}

.easy-tree li:last-child::before {
    height: 42px
}

.easy-tree li.parent_li > span:hover, .easy-tree li.parent_li > span:hover + ul li span {
   /* background: #fff;*/
   /* color: #fff;*/
}
.glyphicon {
    color: red;
}
.easy-tree li.parent_li > span:hover + ul li.li_selected span {
   /* background: #fff;
    color: #7a7a7a;*/
}

.easy-tree li > span > a {
    color: #818181;
    float: left;
    font-family: trebuchet ms;
    font-size: 11pt;
    font-weight: bold;
    padding: 0;
    text-align: left;
    text-decoration: none;
    width: 96%;
}.easy-tree li > span > a > img {
    border-radius: 50%;
    float: left;
    height: 50px;
    margin-right: 10px;
    width: 50px;
}

.easy-tree li > span > span.glyphicon-folder-close, .easy-tree li > span > span.glyphicon-folder-open {
    margin-right: 5px;
}

.easy-tree li.li_selected > span, .easy-tree li.li_selected > span > a {
    background: #01b6b1;
    color: #fff;
}
.li_selected a{
color:#fff;
}

/*.easy-tree li.li_selected > span:hover {
    background: #fafafa;
    color: #ff6c60;
}*/

.easy-tree .easy-tree-toolbar {
    background-color: #fff;display:none;
}

.easy-tree .easy-tree-toolbar > div {
    display: inline-block;
}

.easy-tree .easy-tree-toolbar > div > button {
    border-radius: 0;
    margin: 20px 5px;
}

.easy-tree .easy-tree-toolbar .create .input-group {
    top: -15px;
    margin-left: 5px;
    margin-right: 5px;
}

.easy-tree .easy-tree-toolbar .create .input-group input {
    border-radius: 0;
}

.easy-tree .easy-tree-toolbar .create .input-group button {
    border-radius: 0;
}

=============================

Note:

in above js file you have to replace the {SiteURL} and {domain} and {tenency} with you actual URL of the site.



In summary , you need to follow below steps to create org chart

1.  Create "EasyTree.js"
2. Create "OrgChart.js"
3. Create "orgChart.css"
4. Replace all dynamic variables in OrgChart.js
5. Create div in HTML with class CSS


OrgChart.Js  file uses the PeopleManager REST API of sharepoint 2013.


Thanks,
Sarvesh

No comments:

Post a Comment