Sue Hernandez's Blog

May 22, 2009

MOSS – Graphical Organizational Chart

Filed under: MOSS, Org Chart — Tags: , , — Susan Hernandez @ 1:39 am
MOSS Organizational Chart using Google API

MOSS Organizational Chart using Google API

  

UPDATE:  Added capability to scroll up and down through the hierarchy.   

UPDATE:  I have uploaded a document that contains the full source code
(sorry, wordpress won’t allow me to upload a ZIP file). 
Get It Here:  
Org Chart Code     

We are going to create a simple Org Chart using the Profile Manager and Google’s Org Chart API (http://code.google.com/apis/visualization/documentation/gallery/orgchart.html).    

I recommend starting with the WSP Builder from CodePlex. This add-in to Visual Studio helps you make features quickly by compiling the wsp for you, without you having to create the ddf or manifest files. Using the WSP Builder Template, choose New –> Project –> WSPBuilder –> WSPBuilder Project.    

You will first need to add references in your project to SharePoint and Microsoft.Office.Server, which can typically be found in the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI directory.    

Next, right-click on the WebPart Project Name, and select Add –> New Item. Choose WSPBuilder –> Web Part Feature. Name the Web Part feature, and set your scope. For the purposes of this excercise, we’ll use the Site scope, which sets the feature at the Site Collection level. This will create the appropriate files and folders for you under the 12 directory    

OrgChartPic1    

Let’s start by setting 2 private variables for the class      

  private int orgChartIndex = 0;
  private int levelsToReturn = 3;   

 

The orgChartIndex is used to keep track of which item you’re on, so that you can populate Google’s array items properly.  The levelsToReturn is how many levels deep of hierarchy you wish to display.  

Next, let’s set the Render Method. The Render method simply calls the PopulateOrg method.
UPDATE:  Updated this code to include the “up and down” feature 

        protected override void Render(HtmlTextWriter writer)
        {
            try
            {
                if (Page.IsPostBack)
                {
     		  // This is used to determine if you're navigating up or down the tree
                    //writer.Write(this.Page.Request["__EVENTARGUMENT"]);
                    string loginName = System.Web.HttpUtility.UrlDecode(this.Page.Request["__EVENTARGUMENT"]);
                    if (!string.IsNullOrEmpty(loginName))
                    {
                        if (loginName.StartsWith("UP:"))
                        {
                            writer.Write(PopulateOrg(loginName.Substring(3), true));
                        }
                        else if (loginName.StartsWith("DN:"))
                        {
                            writer.Write(PopulateOrg(loginName.Substring(3), false));
                        }
                    }
                }
                else
                {
                    writer.Write(PopulateOrg(null, false));
                }
            }
            catch (Exception ex)
            {
                writer.Write(ex.Message + " " + ex.StackTrace);
            }
        }   

 

The PopulateOrg method is where we figure out at which level to start listing the tree at. For my example, I’ve started 1 level up – with the logged in user’s Manager. It then populates all of the direct reports down the tree for 3 levels total, which is what we specified in the variable at the top. 

This code assumes that you have the Manager field in the user’s Profile populated. If you have a custom field, just replace PropertyConstants.Manager with the name of your custom field.

UPDATE:  This was modified to use the “Up and Down” feature.

        private string PopulateOrg(string overrideString, bool isUp)
        {
            try
            {
                string s = "";

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    // Open current site
                    using (SPSite secureSite = new SPSite(SPContext.Current.Site.ID))
                    {
                        ServerContext siteContext = ServerContext.GetContext(secureSite);
                        UserProfileManager profManager = new UserProfileManager(siteContext);

                        bool runUser = true;
                        if (!string.IsNullOrEmpty(overrideString))
                        {
                            if (profManager.UserExists(overrideString))
                            {
                                runUser = false;
                                UserProfile clickedUsersProfile = profManager.GetUserProfile(overrideString);

                                if (isUp)
                                {
                                    string clickedManagersLogin = GetUserProfileProperty(clickedUsersProfile, PropertyConstants.Manager);

                                    if (!string.IsNullOrEmpty(clickedManagersLogin))
                                    {
                                        if (profManager.UserExists(clickedManagersLogin))
                                        {
                                            UserProfile clickedManagersProfile = profManager.GetUserProfile(clickedManagersLogin);
                                            s = OutputManager(clickedManagersProfile);
                                        }
                                    }
                                    else
                                    {
                                        s = OutputManager(clickedUsersProfile);
                                    }
                                }
                                else
                                {
                                    s = OutputManager(clickedUsersProfile);
                                }
                            }
                            else
                            {
                                runUser = true;
                            }
                        }

                        if (runUser)
                        {
                            string currentUserLogin = SPContext.Current.Web.CurrentUser.LoginName;
                            if (profManager.UserExists(currentUserLogin))
                            {
                                UserProfile currentUsersProfile = profManager.GetUserProfile(currentUserLogin);
                                string managersLogin = GetUserProfileProperty(currentUsersProfile, PropertyConstants.Manager);

                                if (!string.IsNullOrEmpty(managersLogin))
                                {
                                    if (profManager.UserExists(managersLogin))
                                    {
                                        UserProfile managersProfile = profManager.GetUserProfile(managersLogin);
                                        s = OutputManager(managersProfile);
                                    }
                                    else
                                    {
                                        s = OutputManager(currentUsersProfile);
                                    }
                                }
                                else
                                {
                                    s = OutputManager(currentUsersProfile);
                                }
                            }
                        }
                    }
                });

                return s;
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }

     

The OutputManager method is where we start populating the html and script necessary to set up the org chart. NOTE – use https://www.google.com/jsapi if you are running this code on a secure site.
UPDATE:  Don’t use https, rather upload the jsapi into your document library and reference that.

     

  // ******************************************************************
  // ******************************************************************
  //
  // Some code in this section will need to be replaced with your
  // specific information
  //
  // ******************************************************************
  // ******************************************************************

        private string OutputManager(UserProfile profile)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("\n\n <p><div id='google_org_chart_div_ZoomInfo'>Zoom Level is 100%</div></p>");
            sb.Append("\n\n <p><div style='height=300px;width=1000px;overflow:auto'><div style='zoom=1' id='google_org_chart_div'></div></div></p>");

            sb.Append("\n<a href='javascript:ZoomIn()'>Zoom In</a>");
            sb.Append("&nbsp;&nbsp;<a href='javascript:ZoomOut()'>Zoom Out</a>");

            sb.Append("\n\n<script type='text/javascript' src='http://www.google.com/jsapi'></script>");

            // If you are running HTTPS then download the jsapi from google and put it into a library on your site
            //sb.Append("\n\n<script type='text/javascript' src='https://YOUR-URL-HERE/Shared%20Documents/jsapi'></script>");

            sb.Append("\n<script type='text/javascript'> ");
            sb.Append("\n var zoom = 1; ");
            sb.Append("\n function ZoomOut() { ");
            sb.Append("\n    zoom = zoom - .1; ");
            sb.Append("\n    if (zoom < .10) { zoom = .10; } ");
            sb.Append("\n    var div = document.getElementById('google_org_chart_div'); ");
            sb.Append("\n    var infoDiv = document.getElementById('google_org_chart_div_ZoomInfo'); ");
            sb.Append("\n    infoDiv.innerText = 'Zoom Level is ' + Math.round(zoom*100).toString() + '%'; ");
            sb.Append("\n    if(div.style.zoom) { div.style.zoom = zoom; } ");
            sb.Append("\n } ");
            sb.Append("\n function ZoomIn() { ");
            sb.Append("\n    zoom = zoom + .10; ");
            sb.Append("\n    if (zoom > 2) { zoom = 2; } ");
            sb.Append("\n    var div = document.getElementById('google_org_chart_div'); ");
            sb.Append("\n    var infoDiv = document.getElementById('google_org_chart_div_ZoomInfo'); ");
            sb.Append("\n    infoDiv.innerText = 'Zoom Level is ' + Math.round(zoom*100).toString() + '%'; ");
            sb.Append("\n    if(div.style.zoom) { div.style.zoom = zoom; } ");
            sb.Append("\n } ");
            sb.Append("\n google.load('visualization', '1', {packages:['orgchart']}); ");
            sb.Append("\n google.setOnLoadCallback(drawChart); ");
            sb.Append("\n function drawChart() { ");
            sb.Append("\n    var data = new google.visualization.DataTable(); ");
            sb.Append("\n    data.addColumn('string', 'Name'); ");
            sb.Append("\n    data.addColumn('string', 'Manager'); ");
            sb.Append("\n    data.addColumn('string', 'ToolTip'); ");

            string managersName = GetUserProfileProperty(profile, PropertyConstants.PreferredName);
            managersName = managersName.Replace("'", "");

            sb.Append("\n    data.addRows(1); ");
            sb.Append("\n    data.setCell(0, 0, '" + managersName + "', '" + GetBoxHTML(profile) + "'); ");
            sb.Append("\n    data.setCell(0, 2, '" + managersName + "'); ");

            orgChartIndex = 0;
            foreach (UserProfile directReport in profile.GetDirectReports())
            {
                sb.Append(OutputDirectReport(directReport, managersName, 2));
            }

            sb.Append("\n    var orgchart = new google.visualization.OrgChart(document.getElementById('google_org_chart_div')); ");
            sb.Append("\n    orgchart.draw(data, {allowCollapse:true,allowHtml:true,size:'small'}); ");
            sb.Append("\n } ");
            sb.Append("\n </script>");
            return sb.ToString();
        }

    

        private static string GetUserProfileProperty(UserProfile profile, string propertyName)
        {
            if (profile[propertyName] != null)
            {
                if (profile[propertyName].Value != null)
                {
                    try
                    {
                        return profile[propertyName].Value.ToString();
                    }
                    catch { return string.Empty; }
                }
            }

            return string.Empty;
        }

    

This also calls the OutputDirectReport function, which is a recursive function that gets the strings for the Google data element

    

        private string OutputDirectReport(UserProfile directReport, string managersName, int level)
        {
            orgChartIndex++;

            string s = string.Empty;
            string directReportsName = GetUserProfileProperty(directReport, PropertyConstants.PreferredName);
            directReportsName = directReportsName.Replace("'", "");

            s += "\n    data.addRows(1); ";
            s += "\n    data.setCell(" + orgChartIndex.ToString() + ", 0, '" + directReportsName + "', '" + GetBoxHTML(directReport) + "'); "; // Name and other
            s += "\n    data.setCell(" + orgChartIndex.ToString() + ", 1, '" + managersName + "'); ";       // Manager
            s += "\n    data.setCell(" + orgChartIndex.ToString() + ", 2, '" + directReportsName + "'); ";  // Tooltip

            foreach (UserProfile dirReport in directReport.GetDirectReports())
            {
                if (level < levelsToReturn)
                {
                    s += OutputDirectReport(dirReport, directReportsName, level + 1);
                }
            }

            return s;
        }
What goes inside the box of the org chart node can be HTML code if that’s what you have specified, which we did above. Therefore, we’re calling a method to get the information we want to put in the node in the GetBoxHTML method.
  // ******************************************************************
  // ******************************************************************
  //
  // Some code in this section will need to be replaced with your
  // specific information
  //
  // ******************************************************************
  // ******************************************************************

        private string GetBoxHTML(UserProfile directReport)
        {
            string directReportsName = GetUserProfileProperty(directReport, PropertyConstants.PreferredName);
            directReportsName = directReportsName.Replace("'", "");

            string directReportsLoginName = GetUserProfileProperty(directReport, PropertyConstants.AccountName);

            // If this is the logged in user, highlight it in red.
            if (directReportsLoginName.ToLower() == SPContext.Current.Web.CurrentUser.LoginName.ToLower())
            {
                directReportsName = "<font color=\"red\">" + directReportsName + "</font>";
            }

            string directReportsEmail = GetUserProfileProperty(directReport, PropertyConstants.WorkEmail);
            string directReportsPhone = GetUserProfileProperty(directReport, PropertyConstants.WorkPhone);

            // Replace here...
            string postBackHTMLUp = "<a  id=\"" + orgChartIndex.ToString() + "_Up\" href=\"javascript:" + Page.ClientScript.GetPostBackEventReference(this, "UP:" + directReportsLoginName.Replace("'", "").Replace(@"YOUR-DOMAIN-HERE\", @"YOUR-DOMAIN-HERE\\")) + "\">";
            postBackHTMLUp += "<img src=\"/_layouts/images/ReportsUp.gif\" border=\"0\"></a>";
            postBackHTMLUp = postBackHTMLUp.Replace("'", "\\'");

            // ... and here...
            string postBackHTMLDn = "<a  id=\"" + orgChartIndex.ToString() + "_Dn\" href=\"javascript:" + Page.ClientScript.GetPostBackEventReference(this, "DN:" + directReportsLoginName.Replace("'", "").Replace(@"YOUR-DOMAIN-HERE\", @"YOUR-DOMAIN-HERE\\")) + "\">";
            postBackHTMLDn += "<img src=\"/_layouts/images/ReportsDown.gif\" border=\"0\"></a>";
            postBackHTMLDn = postBackHTMLDn.Replace("'", "\\'");

            // ... and here.
            return string.Format("<b>{4}{5}&nbsp;&nbsp;<a href=\"https://mysite.YOUR-MYSITE-URL-HERE/Person.aspx?accountname={0}\">{1}</a></b><br/><a href=\"mailto:{2}\">{2}</a><br/>{3}", System.Web.HttpUtility.UrlEncode(directReportsLoginName.Replace("'", "")), directReportsName, directReportsEmail, directReportsPhone, postBackHTMLUp, postBackHTMLDn);
        }
Install this solution using the WSP Builder add-in, if you’re coding on your SharePoint Server, or build the WSP file and move that over to your server, and install it using the STSADM.exe tool
We reference a helper method here which simply outputs the property in a string format, checking for nulls and empty strings.

The Silver is the New Black Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.