This guide outlines how I built a dynamic floor plan that gets details out of AD and displays them in SharePoint 2010.
Requirements:
- SharePoint 2010 w/ Enterprise Features (Although it may work with earlier versions)
- Visio 2010 (Although it may work with earlier versions)
- Active Directory
- Some knowledge of VB.net, SharePoint, Active Directory, Java script and CSS
Firstly, this is what it looks like for me, but bear in mind you will be supplying the background image, and choosing the icons you want to use for the ‘people’ entities yourself. I chose one named ‘torso’
Pretty basic looking, Although if you hover over someone you also get their details which are updated from Active Directory
There is two systems working together to bring this up.
Firstly a Visio Web Drawing, that is linked to a spreadsheet in SharePoint, stores the floor plan image and icon locations and popup content. Its displayed on a web part page with a little bit of hidden JSÂ on the side to display the popups.
Behind the scenes I also have an application that periodically polls Active Directory for user information and updates the spreadsheet.
How to do it.
Firstly you need to create some data and put it into a SharePoint document library. if you want you can skip this step and come back to it later, just create a dummy XLSX file with data in it like so. Call it OfficePlanData.XLSX
SpaceID | Name | Title | Phone |
a1026 | user A | demo title | 2349 |
a1020 | User B | demo title | 7151 |
a1031 | User C | demo title | 8004 |
a1028 | user A | demo title | 3429 |
a1027 | User B | demo title | 5339 |
a1025 | User C | demo title | 3188 |
You may want to do this another way, perhaps maintain this file manually, or export data from AD manually etc. I didn’t so I wrote a tool that would take details out of AD, store them in a xlsx file, then upload that to the document library every 60 minutes
Here is the source code to the vb.net project I built (VS2010) that takes user name, title, phone number and extensionattribute1 from AD. (Extension Attribute 1 is storing a unique ‘location’ ID that I call ‘SpaceID’) and upload it to the document Library
It is a console application, requires office 2010 to be added as a service reference. You could easily re-write it to use a different office version I am sure.
Imports System.DirectoryServices Imports System.Web Imports System.Configuration Imports System.Xml Imports System.Net Imports Microsoft.Office.Interop Module Module1    Public sSPURL As String = "http://yoursharepointsite.com"       Public sDocLib As String = "Floorplan" 'the name of the document library    Public sUser As String = "sharepointhelper" 'a user with access to upload files to that document library and do AD lookups    Public sPwd As String = "password123" 'users password    Public sDomain As String = "domainname.local" 'the local domain    Public localfile As String = "C:\Program Files\ADtoFloorplan\OfficePlanData.xlsx" 'the temporary location of the file before upload    Public destinationfile As String = "OfficePlanData.xlsx" 'the filename thats going into the document library    Dim userarray(1000, 6)    Sub Main()        Call getusers()        UploadDocument(localfile, destinationfile)    End Sub    Public Sub getusers()        ' Bind to the users container.        Dim ldapServerName = "domainname"        Dim oRoot As DirectoryEntry = New DirectoryEntry("LDAP://" & ldapServerName & "/OU=Users,DC=DOMAINNAMECHANGEME,DC=local")        Dim oSearcher As DirectorySearcher = New DirectorySearcher(oRoot)        Dim oResults As SearchResultCollection        Dim oResult As SearchResult        Dim name, homedrive, extat1, title, phone        Dim iplacer As DirectoryEntry = New DirectoryEntry        userarray(0, 0) = "SpaceID"        userarray(0, 1) = "Name"        userarray(0, 2) = "Title"        userarray(0, 3) = "Phone"        Try            oResults = oSearcher.FindAll            Dim i As Integer = 1            For Each oResult In oResults                name = oResult.GetDirectoryEntry().Properties("name").Value                title = oResult.GetDirectoryEntry().Properties("title").Value                phone = oResult.GetDirectoryEntry().Properties("telephoneNumber").Value                homedrive = oResult.GetDirectoryEntry().Properties("homeDrive").Value                extat1 = oResult.GetDirectoryEntry().Properties("extensionattribute1").Value                If extat1 <> "" Then 'only populate the file with people who have extension attribute 1 set                    userarray(i, 0) = extat1                    userarray(i, 1) = name                    userarray(i, 2) = title                    userarray(i, 3) = phone                    i = i + 1                End If            Next            WriteSpreadsheetFromArray(userarray, localfile)        Catch e As Exception            Console.WriteLine("Error is " & e.Message)        End Try    End Sub    Public Sub WriteSpreadsheetFromArray(ByVal strOutputArray As Array, Optional ByVal strExcelFileOutPath As String = "")        Dim objxlOutApp As Excel.Application        Dim objxlOutWBook As Excel.Workbook        Dim objxlOutSheet As Excel.Worksheet        Dim objxlRange As Excel.Range        'Try to Open Excel, Add a workbook and worksheet        Try            'Try to Open Excel, Add a workbook and worksheet            objxlOutApp = New Excel.Application            objxlOutWBook = objxlOutApp.Workbooks.Add '.Add.Sheets            objxlOutSheet = DirectCast(objxlOutWBook.Sheets.Item(1), Excel.Worksheet)        Catch ex As Exception            MsgBox("While trying to Open Excel recieved error:" & ex.Message, , "Export to Excel Error")            Try                If Not IsNothing(objxlOutWBook) Then                    objxlOutWBook.Close() 'If an error occured we want to close the workbook                End If                If Not IsNothing(objxlOutApp) Then                    objxlOutApp.Quit() 'If an error occured we want to close Excel                End If            Catch            End Try            objxlOutSheet = Nothing            objxlOutWBook = Nothing            'If Not IsNothing(objxlOutApp) Then            'System.Runtime.InteropServices.Marshal.ReleaseComObject(objxlOutApp) 'This will release the object reference            'End If            objxlOutApp = Nothing            Exit Sub 'An error occured so we don't want to continue        End Try        Try            objxlOutApp.DisplayAlerts = False   'This will prevent any message prompts from Excel (IE.."Do you want to save before closing?")            objxlOutApp.Visible = False   'We don't want the app visible while we are populating it.            'This is the easiest way I have found to populate a spreadsheet            'First we get the range based on the size of our array            objxlRange = objxlOutSheet.Range(Chr(strOutputArray.GetLowerBound(1) + 1 + 64) & (strOutputArray.GetLowerBound(0) + 1) & ":" & Chr(strOutputArray.GetUpperBound(1) + 1 + 64) & (strOutputArray.GetUpperBound(0) + 1))            'Next we set the value of that range to our array            objxlRange.Value = strOutputArray            'This final part is optional, but we Auto Fit the columns of the spreadsheet.            objxlRange.Columns.AutoFit()            If strExcelFileOutPath.Length > 0 Then 'If a file name is passed                Dim objFileInfo As New IO.FileInfo(strExcelFileOutPath)                If Not objFileInfo.Directory.Exists Then 'Check if folder exists                    objFileInfo.Directory.Create() 'If not we create it                End If                objFileInfo = Nothing                objxlOutWBook.SaveAs(strExcelFileOutPath) 'Then we save our file.            End If            objxlOutApp.Visible = True 'Make excel visible        Catch ex As Exception            MsgBox("While trying to Export to Excel recieved error:" & ex.Message, , "Export to Excel Error")            Try                objxlOutWBook.Close() 'If an error occured we want to close the workbook                objxlOutApp.Quit() 'If an error occured we want to close Excel            Catch                Console.WriteLine(ex.Message)            End Try        Finally            objxlOutWBook.Close() 'If an error occured we want to close the workbook            objxlOutApp.Quit()            objxlOutSheet = Nothing            objxlOutWBook = Nothing            'If Not IsNothing(objxlOutApp) Then            '   System.Runtime.InteropServices.Marshal.ReleaseComObject(objxlOutApp) 'This will release the object reference            'End If            objxlOutApp = Nothing        End Try    End Sub    Public Function UploadDocument(ByVal localFile As String, _               ByVal remoteFile As String) As String        '// Read in the local file        On Error GoTo handler        Dim r As Byte()        Dim Strm As System.IO.FileStream = New System.IO.FileStream(localFile, _                    System.IO.FileMode.Open, System.IO.FileAccess.Read)        Dim reader As System.IO.BinaryReader = New System.IO.BinaryReader(Strm)        Dim filecontents As Byte() = reader.ReadBytes(CInt(Strm.Length))        reader.Close()        Strm.Close()        Dim sRemoteFileURL As String        Dim NC As System.Net.NetworkCredential = _            New System.Net.NetworkCredential(sUser, sPwd, sDomain)        sRemoteFileURL = sSPURL & "/" & sDocLib & _                         "/" & Trim(LTrim(RTrim(remoteFile)))        sRemoteFileURL = Replace(sRemoteFileURL, " ", "%20")        sRemoteFileURL = Replace(sRemoteFileURL, "\", "/")        Dim m_WC As WebClient = New WebClient        m_WC.Credentials = NC        r = m_WC.UploadData(sRemoteFileURL, "PUT", filecontents)        Return "TRUE"        Exit Function handler:        Return Err.Description    End Function End Module
I used the following references to build this app
Creating a XLSX file from vb.net: http://www.vbforums.com/showthread.php?t=455162
Uploading a file to a SharepPoint document library: http://www.codeproject.com/KB/sharepoint/File_Shunter.aspx
Getting AD details : http://www.codeproject.com/KB/system/active_directory_in_vbnet.aspx (Started with this, its a bit different now)
You will need to change a few things in there obviously, only as far as
Dim oRoot As DirectoryEntry = New DirectoryEntry("LDAP://" & ldapServerName & "/OU=Users,DC=DOMAINNAMECHANGEME,DC=local")
Though.
I have compiled this as a console application then used task scheduler to run it every hour. you could quite easily also create a service and user your own system.timer if you prefered.
So I am going to assume you now have a xlsx file called officeplandata sitting in your document library which is in the root of your site, and is called ‘floorplan’
open up Visio 2010, first thing we need is the background image, personally I found it was easiest to set the floor plan image as a jpg in the background instead of an image on the page as it means it won’t get in the way when placing other objects on top of it. To do this, add a generic background, then overlay your image on the second tab of the document like so.
You will want your image to be the same aspect ratio as the document (eg letter/a4) so it consumes the entire page.
Now link your data to the xlsx spreadsheet we created earlier. Go to the Data tab, click on Link Data to Shapes, and select Excel Workbook, and link it to the URL of the excel file
Click Next, choose ‘SpaceID’ as the unique Identifier, and finish.
If you have done everything correctly you should now be looking at your blank floor plan with a list of users on the right, something like this.
Flick back to Page-1 if you have not already, search an appropriate icon for your people, as you can see I am using ‘torso’
select the icon, so that its highlighted as you can see above, then drag someone from the right, (the linked data) onto the plan. This will create the shape that is highlighted and link it to the shapeID with all the users details. I have remove the Data Graphics as I found they were too much. to do this, select all icons, then click ‘Data’ from the Ribbon, Data Graphics, then ‘No Data Graphic’
Save the document as a Visio Web Diagram and upload it to the document library.
Assuming you have SharePoint set up correctly you could view the VDW as is and it would show you all the people, but nothing happens when you hover over anyone. Almost there!
Now create a new web part page in the document library, I suggest a Full Page, Vertical
Add a Visio Web Access web part (Under Business Data),
Now create a text file somewhere and paste this into it, save it as floorplan.js or hover.js or whatever you please.
// ECMAScript source code. <script language="javascript"> // OfficePlanData.js // Copyright (c) Microsoft Corporation. All rights reserved. // Description: This script shows how to use shape text overlays //              to display shape data. // // IMPORTANT: To enable this script, ensure that the variable webPartElementId // refers to the HTML ID of the Visio Web Access Web Part that // you want to code against. You can find this ID by searching the source // of a Web Parts page containing a Visio Web Access Web Part for // a tag that contains; you should assign // the value of the id attribute of that tag's grandparent to webPartElementId. // Add a hook into the AJAX Sys.Application.load event, raised after all scripts // have been loaded and the objects in the application have been created // and initialized. Sys.Application.add_load(onApplicationLoad) // Declare global variables for the application. // The HTML tag ID of the Visio Web Access part. var webPartElementID = "WebPartWPQ3";  // The Visio Web Access Web part. var vwaControl; // The current page. var vwaPage; // The collection of all the shapes on the current page. var vwaShapes;  // Function Name:     onApplicationLoad() // Parameters:        None // Description:       This function handles the AJAX Sys.Application.load event. //                     When this event is raised, the function captures references //                     to the Visio Web Access Web Part object and registers //                     the following Visio Web Access specific event handler: // //                     diagramcomplete:       raised when the request for a Web //                                             drawing page finishes. function onApplicationLoad() {        try{                vwaControl= new Vwa.VwaControl(webPartElementID);                vwaControl.addHandler("diagramcomplete", onDiagramComplete);        }        catch(err){        } } // Function Name:     onDiagramComplete() // Parameters:        None // Description:       This function handles the diagramcomplete event, which is //                     raised when a request for a Web drawing page has been completed. //                     When the event is raised, this function captures references //                     to the script's global variables, such as the current page //                     and the collection of shapes on the page. It also sets //                     the zoom to page width and registers the shapemouseenter //                     and shapemouseleave event handlers. function onDiagramComplete() {        try{                vwaPage = vwaControl.getActivePage();                vwaShapes = vwaPage.getShapes();                vwaPage.setZoom(-1);                                            vwaControl.addHandler("shapemouseleave", onShapeMouseLeave);                        vwaControl.addHandler("shapemouseenter", onShapeMouseEnter);        }        catch(err){        } } // Function Name:  onShapeMouseEnter() // Parameters:     source: A reference to the object that raised //                         the shapemouseenter event. //                 args:  The Visio shape ID of the shape the mouse entered. // Description:    This function handles the shapemouseenter event, which //                 is raised when the mouse enters the bounding box of a shape //                 from the active Web drawing page. When the event is raised, //                 this function displays shape data. onShapeMouseEnter = function (source, args) {         var vwaPage = vwaControl.getActivePage();         var vwaShapes = vwaPage.getShapes();         var shape = null;         if (vwaShapes!= null)         {             shape = vwaShapes.getItemById(args);         }         var data = null;         if (shape != null)         {             data = shape.getShapeData();         }         for (var j = 0; j < data.length; j++)        {             if (data[j].label == "Name")                {                var name = data[j].value.toString();                }                    if (data[j].label == "Title")                {                var Title = data[j].value.toString();                }            if (data[j].label == "Phone")                {                 var number = data[j].value.toString();                }                    if (data[j].label == "SpaceID")                {                var office = data[j].value.toString();                }        }                     shape.addOverlay(                            "Overlay",                            '<center><div style="border:solid 5px #000000; font-size: 35; background-color:#b0c4de;">' + name + '<br>' + 'Title: ' + Title + '<br>' + 'Phone #: ' + number + '</div></center>',                            1,                            2,                            600,                            400); } // Function Name:  onShapeMouseLeave // Parameters:     source: A reference to the object that raised //                         the shapemouseleave event. //                 args: The Visio shape ID of the shape the mouse left. // Description:    This function handles the shapemouseleave event, which //                 is raised when the mouse leaves the bounding box of a shape //                 from the active Web drawing page. When the event is raised, //                 this function removes the overlay. onShapeMouseLeave = function (source, args) {                try{        vwaShapes.getItemById(args).removeOverlay("Overlay");       }                catch(err){                } } </script>
Upload this to the document library, then back in your web part page add a Content Editor (Media and Content) and link it to the uploaded javascript, eg: /Floorplan/floorplan.js
Goto Layout, and select ‘hidden’
Save the page and cross your fingers; View it.
With any luck your page will now show the floorplan, hovering over the people will show relevant details. and everything will be done.
You can edit what the box looks like by editing the JS file, line 130 is what you want.
Version 1.0 created 22/09/11
Great article! This is exactly what I was looking for. It took a while to find but well worth it.
Nice idea and solution. Quite innovative. I wonder if making LDAP calls directly to the Active Directory might work. I think that is what this software (floorplanmapper.com) does.
Great article! Question: Is there a way to make this searchable; like if you enter an employee’s name or ID it drops a pin on their location in the floor plan? Thanks in advance.
Awesome article – FYI, there are some off the shelf AD/Floor Plan solutions available now – try Floor Plan Mapper.