Creating Organization Charts for IBM Connections using JavaScript and Google Charts As we all know, IBM Connections has a great report-to-chain widget which shows current user reporting structure. However, sometimes we want to see the whole picture and display the full reporting chart of our organization. And it s quite easy to do since we have all data in IBM Connections. In this document I described how to create nice looking Org Charts based on IBM Connections report-tochain structure and Google Charts JS library. Here is an example of the resulting chart: Using IBM Connections REST API we can get all people managed by specified user as described here: https://www- 10.lotus.com/ldd/lcwiki.nsf/xpAPIViewer.xsp?lookupName=IBM+Connections+6.0+API+Documentation# action=opendocument&res_title=searching_for_a_persons_direct_reports_ic60&content=apicontent Requesting an URL /profiles/atom/peoplemanaged.do?key=<userid> You will get an XML data with all people managed by user as XML entries. <userid> is an internal key generated by Connections for each user. You can also use URL /profiles/atom/peoplemanaged.do?email=<e-mail> if you want. Then, using jquery DOM methods we can get the information we need from that XML and create our chart. We will need a recursive function to walk through all organization tree and get data for each employee. For chart display I used Google Charts Library which have a ready-to-use component for creating Organization Charts https://developers.google.com/chart/interactive/docs/gallery/orgchart
So, we will do it in two steps: 1. Create simple HTML/JS code which can be inserted in ICEC HTML widget on any page. 2. Create custom widget with editable configuration settings. In this document we will work with on-premise version of IBM Connections. There are some differences with IBM Connections Cloud: in URLs of profiles and API calls. However, you can find all the codes for both versions of ICEC widget for Cloud and On-premise as well in zip archive. Let s start! Creating recursive function for retrieving data According to Google Org Chart documentation we need to compose google DataTable object to hold chart data. So we create the object with 2 columns and then add rows with data. // create google data object var data = new google.visualization.datatable(); data.addcolumn('string', 'Name'); data.addcolumn('string', 'Manager'); // add row data.addrow([{v:<userid>,f:<what to display in user box>,<managerid>]); As <userid> we will use IBM Connections user key. You can see user key in the profile link which looks like this In <what to display in user box> section we want to construct nice-looking HTML code with user picture, Name, Title and link to profile. So that s how our table row will look in the code: // add user entry to chart data data.addrow([{v:userkey,f:'<img src="/profiles/photo.do?key='+userkey+'" width="40" height="40"><b><a href="/profiles/html/profileview.do?key='+userkey+'" target="_blank">'+username + '</a></b><br>'+ usertitle, managerid]);
Now we are ready to make a recursive function to get data from connections and put it in chart table. * recursively get data for chart with AJAX calls to Connections API * @param data {google.visualization.datatable hierarchy data * @param id {String parent user ID * @param name {String parent user name function getchartdata(data, id, name) { $.ajax({ type: "GET", async:false, datatype: "xml", url: "/profiles/atom/peoplemanaged.do?key="+id, success: function(xml){ $(xml).find("entry").each(function(){ // get current user data from xml userkey = $(this).find('div.x-profile-key').text(); username = $(this).find('name').text(); usertitle = $(this).find('div.title').text(); // add user entry to chart data data.addrow([{v:userkey,f:'\ <img src="/profiles/photo.do?key='+userkey+'" width="40" height="40"><b>\ <a href="/profiles/html/profileview.do?key='+userkey+'" target="_blank">\ '+username + '</a></b><br>'+ usertitle,id]); // get data for people managed by current user getchartdata(data, userkey, username); ); ); We used AJAX calls to IBM Connections API, got user data from XML and then added it to google table row. We used async:false calls to traverse all tree consequentially.
Creating main function According to Google documentation example we ll wrap the call of our function in another function drawchart: * Main function to call for Chart Draw function drawchart() { // create google data object var data = new google.visualization.datatable(); data.addcolumn('string', 'Name'); data.addcolumn('string', 'Manager'); // add top row data.addrow([{v:topuserid,f:'\ <img src="/profiles/photo.do?key='+topuserid+'" width="40" height="40"><b>\ <a href="/profiles/html/profileview.do?key='+topuserid+'" target="_blank">\ '+topusername+'</a></b><br>'+topusertitle,'']); // create google chart object with chart div var chart = new google.visualization.orgchart(document.getelementbyid('chart_div')); // Start recursive data retrieval getchartdata(data, topuserid, topusername); // Draw the chart, setting the allowhtml option to true for the pictures. chart.draw(data, {allowhtml:true, allowcollapse:true); You can add more options in chart.draw method to use your own colors etc. Now we need only to set root user ID (topuserid variable) and create final HTML for our code. And, of course, we need to include google chart library. <script type="text/javascript" src="//www.gstatic.com/charts/loader.js"></script> <div id="chart_div">retrieving data...</div> <script type="text/javascript"> // Connections ID for root user. // Get it from user profile URL /profiles/html/profileview.do?key=<id> var topuserid = 'cecfe489-4033-48ca-b0b7-df93da53db47'; // well Im lazy so I just set boss name and title right here. // But you can try to get it via Connections API by ID var topusername = 'Dennis Michaels'; var topusertitle = 'CEO'; * recursively get data for chart with AJAX calls to Connections API * @param data {google.visualization.datatable hierarchy data * @param id {String parent user ID * @param name {String parent user name function getchartdata(data, id, name) {
$.ajax({ type: "GET", async:false, datatype: "xml", url: "/profiles/atom/peoplemanaged.do?key="+id, success: function(xml){ $(xml).find("entry").each(function(){ // get current user data from xml userkey = $(this).find('div.x-profile-key').text(); username = $(this).find('name').text(); usertitle = $(this).find('div.title').text(); // add user entry to chart data data.addrow([{v:userkey,f:'\ <img src="/profiles/photo.do?key='+userkey+'" width="40" height="40"><b>\ <a href="/profiles/html/profileview.do?key='+userkey+'" target="_blank">\ '+username + '</a></b><br>'+ usertitle,id]); // get data for people managed by current user getchartdata(data, userkey, username); ); ); * Main function to call for Chart Draw function drawchart() { // create google data object var data = new google.visualization.datatable(); data.addcolumn('string', 'Name'); data.addcolumn('string', 'Manager'); // add top row data.addrow([{v:topuserid,f:'\ <img src="/profiles/photo.do?key='+topuserid+'" width="40" height="40"><b>\ <a href="/profiles/html/profileview.do?key='+topuserid+'" target="_blank">\ '+topusername+'</a></b><br>'+topusertitle,'']); // create google chart object with chart div var chart = new google.visualization.orgchart(document.getelementbyid('chart_div')); // Start recursive data retrieval getchartdata(data, topuserid, topusername); // Draw the chart, setting the allowhtml option to true for the pictures. chart.draw(data, {allowhtml:true, allowcollapse:true); * Theres a problem with loading google library when using the code * inside ICEC HTML widget even using $(document).ready * so this small workaround just to be sure that google chart library is fully loaded. function LoadGoogle() { if(typeof google!= 'undefined')
{ google.charts.load('current', {packages:["orgchart"]); google.charts.setonloadcallback(drawchart); else { // Retry later... settimeout(loadgoogle, 30); LoadGoogle(); </script> The code is ready and we are to insert it in ICEC HTML widget. You can also use this code in classic IBM Connections iwidget or Portlet or any other application. But don t forget to manually set your own top user ID.
And here s the result!
Creating custom ICEC widget Now let s transform our code into custom ICEC widget. Use ICEC documentation and tutorial to better understand ICEC widget API: https://www.ibm.com/support/knowledgecenter/en/ssl3jx/icec/cec-custom-widget-api.html https://www.ibm.com/developerworks/collaboration/library/icec-widget-lab/index.html To make custom widget we will split our code into 2 parts: 1. org_chart_onpremise.html HTML code with div container. Actually, we can put chart content directly into widget container but having our own HTML will give us more control. 2. org_chart_onpremise.js JS code We also need to add some code to custom.js ICEC built-in file to define our custom widget. org_chart_onpremise.html <div id="chart_div">retrieving data...</div> org_chart_onpremise.js (function ($, W) { // get or create and expose the XCC Object var X = (function () {W.XCC = W.XCC {; return W.XCC; ()); // Connections ID for root user. // Get it from user profile URL /profiles/html/profileview.do?key=<id> var topuserid = ''; // well Im lazy so I just set boss name and title right here. // But you can try to get it via Connections API by ID var topusername = ''; var topusertitle = ''; var targetdiv = null; var p = W.XCC.P.getProfile(); * recursively get data for chart with AJAX calls to Connections API * @param data {google.visualization.datatable hierarchy data * @param id {String parent user ID * @param name {String parent user name function getchartdata(data, id, name) { $.ajax({ type: "GET", async:false, datatype: "xml", url: "/profiles/atom/peoplemanaged.do?key="+id, success: function(xml){
$(xml).find("entry").each(function(){ // get current user data from xml userkey = $(this).find('div.x-profile-key').text(); username = $(this).find('name').text(); usertitle = $(this).find('div.title').text(); console.log(username); // add user entry to chart data data.addrow([{v:userkey,f:'\ <img src="/profiles/photo.do?key='+userkey+'" width="40" height="40"><b>\ <a href="/profiles/html/profileview.do?key='+userkey+'" target="_blank">\ '+username + '</a></b><br>'+ usertitle,id]); // get data for people managed by current user getchartdata(data, userkey, username); ); ); * Main function to call for Chart Draw function drawchart() { // create google data object var data = new google.visualization.datatable(); data.addcolumn('string', 'Name'); data.addcolumn('string', 'Manager'); // add top row data.addrow([{v:topuserid,f:'\ <img src="/profiles/photo.do?key='+topuserid+'" width="40" height="40"><b>\ <a href="/profiles/html/profileview.do?key='+topuserid+'" target="_blank">\ '+topusername+'</a></b><br>'+topusertitle,'']); // create google chart object with chart div var chart = new google.visualization.orgchart(targetdiv.find("#chart_div")[0]); // Start recursive data retrieval getchartdata(data, topuserid, topusername); // Draw the chart, setting the allowhtml option to true for the pictures. chart.draw(data, {allowhtml:true, allowcollapse:true); * Callback function for ICEC widget * @param container$ {Jquery-Object the HTML-container in the Widget.. * @param widgetdata {Object The widget data object with widget settings function LoadChart(container$, widgetdata) { targetdiv = container$; topuserid = X.T.getCustomPropertyValue("OrgChartWidgetTopID", widgetdata); topusername = X.T.getCustomPropertyValue("OrgChartWidgetTopName", widgetdata); topusertitle = X.T.getCustomPropertyValue("OrgChartWidgetTopTitle", widgetdata);
if(!topuserid) { topuserid = p.getprofilekey(); topusername = p.getname(); topusertitle = p.gettitle(); google.charts.load('current', {packages:["orgchart"]); google.charts.setonloadcallback(drawchart); // define this function in dependency of nothing X.define([], function () { return LoadChart; ); // END X.define // END X.define (XCC.jQuery jquery, window)); We upload our files in ICEC Customization Files.
Now we need to define our custom widget for ICEC in custom.js file. Open it in editor
and add the following code inside init() function. ********* START: INSERT THIS SECTION INTO CUSTOM.JS init() FUNCTION ********* * ORG CHART widget function. Includes HTML code, google charts JS library * and widget JS code. * @param {[Jquery-Object] container$ [the HTML-container in the Widget.. ] * @param {[Object] widgetdata [The widget data] * function orgchartwidget(container$, widgetdata) { $.get("/xcc/rest/files/custom/org_chart_onpremise.html",function(data) { container$.html(data); ); widgetdata.contenttype = widgetdata.contenttype.trim(); // require google charts JS, widget JS code, and call LoadChart XCC.require(["profiles"], function () { XCC.require(["//www.gstatic.com/charts/loader.js"], function () { XCC.require(["/xcc/rest/public/custom/org_chart_onpremise.js"], function(loadchart) { LoadChart(container$, widgetdata); ); ); ); * ORG CHART edit settings: widget title, and custom properties: * topid, topname, toptitle * @param container$ {jquery the parent node that will hold the editor * @param widgetdata {Object the widget object to work on * * @return a HTML-String, Jquery-Object or an array of Jquery-Objects! * function orgcharteditor(container$, widgetdata) { return [XCC.U.createTextInputOnTheFly("Widget Title", widgetdata.title, "title"),
XCC.U.createTextInputOnTheFly("Top user ID", XCC.T.getCustomPropertyValue("OrgChartWidgetTopID", widgetdata), "orgchartwidgettopid"), XCC.U.createTextInputOnTheFly("Top user Name", XCC.T.getCustomPropertyValue("OrgChartWidgetTopName", widgetdata), "orgchartwidgettopname"), XCC.U.createTextInputOnTheFly("Top user Title", XCC.T.getCustomPropertyValue("OrgChartWidgetTopTitle", widgetdata), "orgchartwidgettoptitle") ]; * ORG CHART save settings to custom properties. * @param {[type] container$ [the Editor as a Jquery-Object] * @param {[type] widgetdata [the widget data] function orgchartsave(container$, widgetdata) { widgetdata.title = container$.find("input[name=title]").val(); XCC.T.setXccPropertyString(widgetData,"OrgChartWidgetTopID", container$.find("input[name=orgchartwidgettopid]").val()); XCC.T.setXccPropertyString(widgetData,"OrgChartWidgetTopName", container$.find("input[name=orgchartwidgettopname]").val()); XCC.T.setXccPropertyString(widgetData,"OrgChartWidgetTopTitle", container$.find("input[name=orgchartwidgettoptitle]").val()); * ORG CHART register a Custom Widget * @param name {String Name of the Custom Widget, * @param icon {String name of the icon. Without the "fa-" at the beginning. * @param createcustomwidget {function Function which should be called when rendering * @param CreateCustomEditor {function optional: Use an own Editor instance! * @param synchuitowidgetdataobject {function optional: Synch your Data * from the UI into the Widget. * @param dontshowin {String optional XCC.W.registerCustomWidget("Org Chart","flag",orgChartWidget,orgChartEditor, orgchartsave ); ********* END: INSERT THIS SECTION INTO CUSTOM.JS init() FUNCTION ********* Following the example of Custom Widget which already placed in init() we created 3 functions: orgchartwidget which invokes the code of our widget. Here we use require module predefined in ICEC to load our google library instead of <script> tag. orgcharteditor to edit our custom settings. orgchartsave to save settings into XCC custom properties Then we registered our widget with registercustomwidget method. Now let s test how it works!
First added our widget shows hierarchy down from current user. Let s define the head of our Organization.
Codes You can find all the codes used in this document here in archive file: https://ibm.box.com/s/d4j46yyfyfut4xh5hr70l45q7lhdk108 Ivan Mikhalychev IBM Collaboration Solutions Technical professional ivan.mikhalychev@ru.ibm.com Special thanks to Stefano Pogliani, IBM Technical Sales Collaboration for useful materials and demo!