Skype for Business Presence Dashboard Revisited


Wednesday, 16 September 2015

Share with: 
 

Earlier this year, I released the source code for my Lync or Skype for Business Presence Dashboard - an open source dashboard using the Skype Web SDK. I've now updated this dashboard with a new look & feel, new features and more error handling. It's a work in progress, so please feel free to suggest improvements or fixes!

The original version can be found at my blog post about Creating a Presence Dashboard.

A recap - what is a Presence Dashboard?

A Presence Dashboard is a web-based (or application-based) tool that provides a single view of all your Lync or Skype for Business contacts and their current status.

A number of commercial dashboards are available in the market today, however you can easily build your own (or download mine) for free using the recently released Skype Web SDK.

New Presence Dashboard

 

Overview

The Skype for Business (formerly Lync 2013) client displays presence effectively, next to each person in the groups or status list. This information, however, can’t be easily exported or displayed on a web site, as it is part of the client application.

A Presence Dashboard displays this same information on a web site, updated in real-time, which is handy for public or more practical display purposes.


 

The New Dashboard Features

I've updated the dashboard to include a much more neat and professional layout, as shown below. The dashboard will display your contacts photograph, status or availability, and their full name. It will also display a link to start an Instant Message with each contact.

In the interest of privacy, I've hidden some surnames and photographs from the screenshots below.

New Features

  • Ability to turn on/off logging details
  • Ability to turn on/off the status or availability legends (useful to save space on larger dashboards)
  • Options to exclude off-line contacts (not much point seeing them normally since you can't interact with them)
  • Options to sort your contacts in the dashboard.

You may be asking, 'Why do we need to sort contacts? Can't Skype do that?' Well, yes and no. Lync or Skype for Business can of course sort your contacts, however the Skype Web SDK returns contacts and presence status asynchronously. This means that it returns contacts as they respond with their status, which isn't necessarily in any particular order. The benefit is that the dashboard builds quickly, the downside is that contacts are rarely in order.

 

Technology Required

To use this presence dashboard, all you need is the SDK's prerequisites installed on your Lync or Skype for Business servers, a web server to host the application, and of course your Lync or Skype for Business account details.

I've used the web framework bootstrap, in order to provide a clean and responsive layout, as well as jQuery, to simplify my JavaScript.

This project assumes you have an understanding of JavaScript and jQuery. If not, the internet is full of great tutorials, or check out Puralsight.

This project doesn’t require any server side components, apart from a Lync 2013 or Skype for Business environment that supports the UCWA framework. If don’t already have UCWA installed on your environment, pop over here to learn how to install it.

 

Important Prerequisites

The Skype Web SDK requires that the UCWA framework is installed and enabled on your Front-End, Edge and Director servers. Click here to view instructions for how to install UCWA. The Skype Web SDK simply won't function without these steps.

 

The Code

The application performs a few basic steps, which are outlined below.

  1. Initialize the SDK
  2. Authenticate with user credentials
  3. Get the user’s list of contacts
  4. Subscribe to presence changes for each contact
  5. Logout
  6. Miscellaneous Functions

The full code of for this application is listed below.

If you’ve already built an application using the Skype Web SDK, you can probably skip steps 1 & 2.

Step 1 – Initializing the SDK

The first step is to actually initialize the SDK. This requires the inclusion of the actual SDK in your HTML file, and this is made very easy as Microsoft conveniently provide this on their CDN.


<script src="https://swx.cdn.skype.com/shared/v/1.1.25.0/SkypeBootstrap.min.js"></script>

The version of the script may change from time to time, so it’s always best to check http://developer.skype.com regularly.

The SDK is exposed to JavaScript with the "Skype." prefix, which is case-sensitive.

Within your JavaScript file, we now need to initialize the SDK, which we can achieve by calling the SDK’s initialize method.


var Application
var client;
Skype.initialize({
    apiKey: 'SWX-BUILD-SDK',
        }, function (api) {
            Application = api.application;
            client = new Application();
        }, function (err) {
            log('An error occurred initializing the application: ' + err);
});
                

In this example, we’re creating an application object, called (obviously) Application. The Application object is created by calling the application constructor and is the entry point to the SDK, and we will create a new instance of this called client.

Step 2 - Authenticate with user credentials

Once the application has been initialized, the next step is to authenticate against the server using your sign-in credentials.


// start signing in
client.signInManager.signIn({
    username: 'matthew@contoso.com',
    password: 'p@ssw0rd!'
})
            


In this project, we really don’t want to hard-code credentials, so instead lets collect them from forms on the web page.


// start signing in
client.signInManager.signIn({
    username: $('#username').text(),
    password: $('#password').text()
})
            


We should handle authentication errors gracefully, so lets add a handler to let us know when authentication is successful, or when it fails.


// start signing in
application.signInManager.signIn({
    username: $('#username').text(),
    password: $('#password').text()
}).then(
    //onSuccess callback
    function () {
        // when the sign in operation succeeds display the user name
        log('Signed in as'+ application.personsAndGroupsManager.mePerson.displayName());
    },
    //onFailure callback
    function (error) {
        // if something goes wrong in either of the steps above,
        // display the error message
        log(error || 'Cannot sign in');
    })
});
            

To help know whether the Application remains signed in, or indeed it’s state at any time, it’s useful to set up a subscription to the SignInManager.state.change method.


// whenever state changes, display its value
application.signInManager.state.changed(function (state) {
    log('Application has changed its state to: ' + state);
});
            

Step 3 - Get the user’s list of contacts

Before we can display anyone’s presence status, we need to collection of contacts ('persons’) and iterate through them, retrieving key information to display.

This is achieved by the rather verbose call to the persons.get() method.


client.personsAndGroupsManager.all.persons.get().then(function (persons) {
        ...
})
            

A person object in Skype Web SDK represents a single person, and contains all the information the user publishes from presence information and a photograph, to telephone numbers, job title and current location.

When then iterate through the persons.get() object to discover each contact.


persons.forEach(function (person) {
    person.displayName.get().then(function (name) {
        var tag = $('<p>').text(name);
        log('Person found: ' + tag);
    })
});
            

Step 4 - Subscribe to presence changes for each contact

As we iterate through the list of contacts in Step 3, we need to attach a subscription request to each – triggering an update to the card when the contact’s subscription status changes – for example from Online to Away.

This is as simple as:


person.status.subscribe();
            


Of course, we need to catch the change event, and actually update the card appropriately.


person.status.changed(function (status) {
    log(name + ' availability status is changed to ' + status);
    var d = new Date();
    var curr_hour = d.getHours();
    var curr_min = d.getMinutes();
    var curr_sec = d.getSeconds();
    var new_presence_state = '';
    if (status == 'Online') {
        new_presence_state = 'alert alert-success';
    }
    else if (status == 'Away') {
        new_presence_state = 'alert alert-warning';
    }
    else if (status == 'Busy') {
        new_presence_state = 'alert alert-danger';
    }
    else {
    if ($('#showoffline').is(":checked")) {
        new_presence_state = 'alert alert-info';
    }
    }
    if (new_presence_state != '') {
        log(name + ' has a new presence status of ' + new_presence_state);
    }
});
            

Step 5 - Logout

When you want to close the dashboard, it’s recommended that you log out using the SDK’s functions, as opposed to just closing the browser or navigating away. This ensures that the session to Lync 2013 or Skype for Business is closed correctly.

(Code from https://msdn.microsoft.com/EN-US/library/dn962162%28v=office.16%29.aspx )


// when the user clicks on the "Sign Out" button
$('#signout').click(function () {
    // start signing out
    application.signInManager.signOut()
    .then(
    // onSuccess callback
    function () {
        // and report the success
        log('Signed out');
    },
    // onFailure callback
    function (error) {
        // or a failure
        log(error || 'Cannot sign in');
    });
});
         

Download

You can download the code to this project from GitHub, or check out the code below.

Download Follow @matthewproctor

 

The Full Code

<!doctype html>
<html>
<head>
    <title>Matthew's Presence Dashboard</title>
    <!-- SkypeWeb library requires IE compatible mode turned off -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <link rel="shortcut icon" href="//www.microsoft.com/favicon.ico?v2">

    <!-- The jQuery library written by John Resig (MIT license) -->
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.3.min.js"></script>

    <!-- SkypeWebSDK Bootstrap Libray -->
    <script src="https://swx.cdn.skype.com/shared/v/1.1.23.0/SkypeBootstrap.min.js"></script>

    <!-- Load Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    <!-- JavaScript files -->
    <script src="index.js"></script>
    <script src="navbar.js"></script>
    <script src="sort.js"></script>

    <!--- Cascading Style Sheets (CSS) -->
    <link href="index.css" rel="stylesheet">
    <link href="sidebar.css" rel="stylesheet">

</head>
<body>

    <nav id="thenavbar" class='sidebar sidebar-menu-collapsed'>

        <ul>
            <li>
                <a class='expandable' href='#' title='Dashboard'>
                    <span class='glyphicon glyphicon-home collapsed-element'></span>
                    <span class='expanded-element'>Home</span>
                </a>
            </li>
            <li>
                <a id="refreshdashboard" class='expandable' href='#' title='Refresh the Dashboard'>
                    <div id="refreshicon" class='glyphicon glyphicon-refresh collapsed-element'></div>
                    <span class='expanded-element'>Refresh</span>
                </a>
            </li>
            <li>
                <a id="togglesettings" class='expandable' href='#' title='Show Settings'>
                    <span class='glyphicon glyphicon-cog collapsed-element'></span>
                    <span class='expanded-element'>Settings</span>
                </a>
            </li>
            <li>
                <a id="togglelegend" class='expandable' href='#' title='Display Presence Legend'>
                    <div id="refreshicon" class='glyphicon glyphicon-tasks collapsed-element'></div>
                    <span class='expanded-element'>Display Presence Legend</span>
                </a>
            </li>
            <li>
                <a id="togglelogs" class='expandable' href='#' title='Toggle Logs'>
                    <span class='glyphicon glyphicon-list collapsed-element'></span>
                    <span class='expanded-element'>Logs</span>
                </a>
            </li>
            <li>
                <a id="aboutthedashboard" class='expandable' href='#' title='About'>
                    <span class='glyphicon glyphicon-question-sign collapsed-element'></span>
                    <span class='expanded-element'>About</span>
                </a>
            </li>
            <li>
                <a class='expandable' href='#' id='logout-icon' title='Logout'>
                    <span class='glyphicon glyphicon-off collapsed-element'></span>
                    <span class='expanded-element'>Log Out</span>
                </a>
            </li>
        </ul>
    </nav>

    <div class="container" id="layout">

        <h1>Skype for Business Presence Dashboard</h1>

        <div class="signinframe">

            <div id="loginbox">
                <div>Login</div>
                <div id="address" contenteditable="true" class="input form-control"></div>
                <div>Password</div>
                <input type="password" id="password" name="password" class="input form-control" />
                <div id="signin" class="button">Sign In</div>
            </div>

            <div id="welcomebox" class="welcomebox">
                <br />
                <div class="row">
                    <div class="col-md-1"><img src="assets/arrow-left.png" /></div>
                    <div class="col-md-10">
                        <h4>Click the refresh icon (<i id="refreshicon" class='glyphicon glyphicon-refresh'></i>) on the left to build the dashboard.</h4>
                        <p>The process to build the dashboard may initially take a few moments, depending on the number of contacts you have, and the performance of your Lync 2013 or Skype for Business 2015 environment.</p>
                        <p>Contacts will also appear in a semi-random order, as they provide presence updates individually to the server.</p>
                    </div>
                </div>
            </div>

            <div id="controlbox">
                <div id="settings" class="settings">
                    <div class="row">
                        <div class="col-md-3 settingsbox">Settings:</div>
                        <div class="col-md-3"><div class="checkbox"><label><input type="checkbox" checked="" id="showoffline"> Show Offline Contacts</label></div></div>
                        <div class="col-md-3"><div class="checkbox"><label><input type="checkbox" checked="" id="sortbyname"> Sort by Name</label></div></div>
                        <div class="col-md-3"><div class="checkbox"><label><input type="checkbox" checked="" id="sortbystatus"> Sort by Status</label></div></div>
                    </div>
                </div>
            </div>

            <div class="row presencelegenddiv" id="presencelegenddiv">
                <div class="col-sm-1"><p class="alert">Legend:</p></div>
                <div class="col-sm-2"><p class="alert alert-success"><img src="statusicons/presence-online.png" height="24" /> Online</p></div>
                <div class="col-sm-2"><p class="alert alert-warning"><img src="statusicons/presence-away.png" height="24" /> Away</p></div>
                <div class="col-sm-2"><p class="alert alert-danger"><img src="statusicons/presence-busy.png" height="24" /> Busy</p></div>
                <div class="col-sm-3"><p class="alert alert-dnd"><img src="statusicons/presence-dnd.png" height="24" /> Do Not Disturb</p></div>
                <div class="col-sm-2"><p class="alert alert-info"><img src="statusicons/presence-offline.png" height="24" /> Offline</p></div>
            </div>

            <div id="results"></div>
        </div>

        <br />
        <div class="container" id="thedashboard">

            <div class="row" id="dashboardoutput">
            </div>

            <div class="row">
                <div class="col-md-12" id="updatelabel"></div>
            </div>
        </div>

        <div class="container" id="loggingdiv">
            <div id="logging_box" contenteditable="false" class="code"><b>Event Logs<br /></b></div>
        </div>

        <div class="modal">
            <div class="modal-header" id="modal-header-bar">
                <h3>About the Presence Dashboard <a class="close-modal" href="#">×</a></h3>
            </div>
            <div class="modal-body">
                <p>
                    The Lync and Skype for Business Presence Dashboard was created by <a href="http://www.matthewproctor.com/" target="_blank">Matthew Proctor</a>, and is available as a free, open-source project.
                </p>
                <p>You are free to use this software so long as you attribute the original author.</p>
                <p>This project uses a number of frameworks, including:</p>
                <ul>
                    <li><a href="https://www.matthewproctor.com/skype-web-sdk/" target="_blank">Skype Web SDK</a></li>
                    <li><a href="http://www.getbootstrap.com" target="_blank">Bootstrap</a></li>
                    <li><a href="http://www.jquery.org" target="_blank">jQuery</a></li>
                </ul>
                <p>Learn more about this project at <a href="http://www.matthewproctor.com/" target="_blank">Matthew's Blog</a>.</p>
            </div>

        </div>
        <div class="modal-backdrop"></div>

        <div id="sbs" class="button">Sort by Presence</div>
        <div id="sbn" class="button">Sort by Name</div>
    </div>
    
</body>
</html>

/**
 *
 * Matthew Proctor's Presence Dashboard
 * Loads all contacts for the signed-in user, and displays them in a 'Presence Dashboard' style approach
 * http://www.matthewproctor.com/
 * 
 */

// Two dynamic arrays to store our div id's and email addresses
var name_ids = new Array();
var emailAddresses = new Array();
var category_sort_order = ['Online', 'Away', 'Busy', 'DoNotDisturb', 'Offline'];

// this function replaces a broken image with a nice blue Lync/Skype icon - indicating the contact doesn't have a photo.
function imgError(image) {
    image.onerror = "";
    image.src = "assets/noimage.png";
    return true;
}

function pause(howlongfor) {
    log("Pausing for " + howlongfor + "ms");
    var currentTime = new Date().getTime();
    while (currentTime + howlongfor >= new Date().getTime()) { }
}

function log(texttolog) {
    var d = new Date();
    var time = padLeft(d.getHours(), 2) + ":" + padLeft(d.getMinutes(), 2) + ":" + padLeft(d.getSeconds(), 2) + ":" + padLeft(d.getMilliseconds(), 3);
    $('#logging_box').prepend(time + ": " + texttolog + "<br>");
}
function padLeft(nr, n, str) {
    return Array(n - String(nr).length + 1).join(str || '0') + nr;
}

var bs_header = '';  // reserved for future use
var bs_footer = '';  // reserved for future use  

$(function () {
    'use strict';

    $('#controlbox').hide();
    $('#thedashboard').hide();
    $('#thenavbar').hide();
    $('#settings').hide();
    $('#loggingdiv').show();
    $('#presencelegenddiv').hide();
    $('#welcomebox').hide();

    $('#showoffline').attr('checked', false);
    $('#sortbyname').attr('checked', false);
    $('#sortbystatus').attr('checked', false);

    log("App Loaded");

    var Application
    var client;
    Skype.initialize({
        apiKey: 'SWX-BUILD-SDK',
    }, function (api) {
        Application = api.application;
        client = new Application();
        log("Client Created");
       
    }, function (err) {
        log('some error occurred: ' + err);
    });

     

    //start the refresh spinner
    function spinner_start() {
        $("#refreshicon").addClass("spin");
    }
    //stop the refresh spinner
    function spinner_stop() {
        $("#refreshicon").removeClass("spin");
    }

   
    function build_dashboard() {
        log('Building Dashboard...');
        $('#welcomebox').hide();
        
        //clear the name cache
        name_ids.length = 0;
        emailAddresses.length = 0;

        spinner_start();

        // clear the existing dashboard
        $('#dashboardoutput').empty();

        var thestatus = '';
        var destination = '';
        var personuri = "";

        client.personsAndGroupsManager.all.persons.get().then(function (persons) {
            // `persons` is an array, so we can use Array::forEach here
            log('Found Collection');
            $('#dashboardoutput').append(bs_header);

            persons.forEach(function (person) {
                // the `name` may not be loaded at the moment
                personuri = "";

                person.displayName.get().then(function (name) {

                    // subscribe to the status change of everyone - so that we can see who goes offline/away/busy/online etc.
                    person.status.changed(function (status) {
                        $("#updatelabel").val(name + ' is now ' + status);

                        var d = new Date();
                        var curr_hour = d.getHours();
                        var curr_min = d.getMinutes();
                        var curr_sec = d.getSeconds();

                        var new_presence_state = '';
                        if (status == 'Online') {
                            new_presence_state = 'alert alert-success';
                        }
                        else if (status == 'DoNotDisturb') {
                            new_presence_state = 'alert alert-dnd';
                        }
                        else if (status == 'Away') {
                            new_presence_state = 'alert alert-warning';
                        }
                        else if (status == 'Busy') {
                            new_presence_state = 'alert alert-danger';
                        }
                        else {
                            if ($('#showoffline').is(":checked")) {
                                new_presence_state = 'alert alert-info';
                            }
                        }
                        if (new_presence_state != '') {
                            var name_id = name.replace(/[^a-z0-9]/gi, '');
                            $('#status' + name_id).attr('class', new_presence_state);
                        }
                        if ($('#sortbyname').is(":checked")) {
                            sort_dashboard_by_name();
                        }
                    });
                    person.status.subscribe();

                    // if the name is their email address, drop the domain component so that it's a little more readable
                    var name_shortened = name.split("@")[0];
                    var name_shortened = name_shortened.split("(")[0];
                    var name_id = name.replace(/[^a-z0-9]/gi, '');

                    //if the displayname is actually an email address,
                    if (name.contains("@") && name.contains(".")) {
                        personuri = name;
                    }

                    person.emails.get().then(function (emails) {
                        var json_text = JSON.stringify(emails, null, 2).toString();
                        json_text = json_text.replace("[", "");
                        json_text = json_text.replace("]", "");
                        var obj = $.parseJSON(json_text);
                        personuri = obj['emailAddress'];
                        name_ids.push(name_id);
                        emailAddresses.push(personuri);
                    });
                     

                    person.status.get().then(function (status) {

                        //select a bootstrap helper style that reasonably approximates the Skype presence colours.
                        var presence_state = '';
                        if (status == 'Online') {
                            presence_state = 'alert alert-success';
                            destination = 'contact_online';
                        }
                        else if (status == 'Away') {
                            presence_state = 'alert alert-warning';
                            destination = 'contact_away';
                        }
                        else if (status == 'Busy') {
                            presence_state = 'alert alert-danger';
                            destination = 'contact_busy';
                        }
                        else if (status == 'DoNotDisturb') {
                            presence_state = 'alert alert-dnd';
                            destination = 'contact_dnd';
                        }
                        else {
                            if ($('#showoffline').is(":checked")) {
                                presence_state = 'alert alert-info';
                                destination = 'contact_offline';
                            }
                        }
                        // if a presence has been determined, display the user.
                        if (presence_state != '') {

                            //now get their Photo/Avatar URL
                            person.avatarUrl.get().then(function (url) {
                                $('#dashboardoutput').append(build_card(name, name_id, presence_state, url, name_shortened, personuri, status));
                            }).then(null, function (error) {
                                $('#dashboardoutput').append(build_card(name, name_id, presence_state, '', name_shortened, personuri, status));
                            });
                            if ($('#sortbyname').is(":checked")) {
                                sort_dashboard_by_name();
                            }
                        }
                    });

                });
            });


            $('#dashboardoutput').append(bs_footer);
            spinner_stop();

        }).then(null, function (error) {
            log(error || 'Something went wrong.');
        });
        log('Finished');
        if ($('#sortbyname').is(":checked")) {
            sort_dashboard_by_name();
        }

    }

    
   
    function build_card(name, name_id, presence_state, url, name_shortened, personuri, status) {
        var temp = "";
        temp = temp + "<div category=\"" + status + "\" class=\"col-sm-3 \" id=\"" + name + "\"><p id=\"status" + name_id + "\" class=\"" + presence_state + "\">";
        if (url != "") {
            temp = temp + "<img hspace=5 src=\"" + url + "\" width=32  onError=\"this.onerror=null;this.src='assets/noimage.png';\" />";
        }
        temp = temp + name_shortened;


        if (personuri != "") {
            //http://stackoverflow.com/questions/15385207/how-to-change-href-attribute-using-javascript-after-opening-the-link-in-a-new-wi
            temp = temp + "<a onClick=\"clickAddress('" + name_id + "')\" href=\"#\"><i class=\"glyphicon glyphicon-comment\" style=\"float:right; color:#0094ff; font-size:14pt; top:5px;\"></i></a>";
        }

        temp = temp + "<a onClick=\"clickAddress('" + name_id + "')\" href=\"#\"><i class=\"glyphicon glyphicon-info-sign\" style=\"float:right; padding-right:2px; color:#0094ff; font-size:14pt; top:5px;\"></i></a>";

        temp = temp + "</p></div>";
        return temp;
    }

    $('#everyone').click(function () {
        build_dashboard();
    })

    function signin() {
        $('#signin').hide();
        log('Signing in...');
        // The asynchronous "signIn" method
        client.signInManager.signIn({
            username: $('#address').text(),
            password: $('#password').text()
        }).then(function () {
            log('Logged In Succesfully');
            $('#controlbox').show();
            $('#loginbox').hide();
            $('#thedashboard').show();
            $('#thenavbar').show();
            $('#welcomebox').show();
        }).then(null, function (error) {
            // if either of the operations above fails, tell the user about the problem
            //console.error(error);
            log('Sorry, we were not able to sign in successfully. Error' + error);
            $('#signin').show()
        });
    }

    function signout() {
        log("Signing Out");
        client.signInManager.signOut().then(
            function () {
                log('Signed out');
                $('#controlbox').hide();
                $('#thedashboard').hide();
                $('#loginbox').show();
                $('#signin').show();
                $('#thenavbar').hide();
                $('#thenavbar').hide();
                $('#settings').hide();
                $('#presencelegenddiv').hide();
                $('#welcomebox').hide();
            },
        function (error) {
            log('Error signing out:' + error);
        });
    }

    // sort dashboard by presence status
    $('#sbs').click(function () {
        log("Clicked Sort by Status");
        sort_dashboard_by_status();
    });

    // sort dashboard by presence status
    $('#sbn').click(function () {
        log("Clicked Sort by Name");
        sort_dashboard_by_name();
    });

    // when the user clicks the refresh/recycle button on the navbar
    $('#refreshdashboard').click(function () {
        build_dashboard();
    });

    // when the user clicks the show-offline checkbox, refresh the dashboard
    $('#showoffline').click(function () {
        build_dashboard();
    });

    // when the user clicks the sort by name checkbox
    $('#sortbyname').click(function () {
        if ($('#sortbyname').is(":checked")) {
            $('#sortbystatus').attr('checked', false);
            sort_dashboard_by_name();
        }
    });

    // when the user clicks the sort by status name
    $('#sortbystatus').click(function () {
        if ($('#sortbystatus').is(":checked")) {
            $('#sortbyname').attr('checked', false);
            sort_dashboard_by_status();
        }
    });

    // when the user clicks the "Sign In" button
    $('#signin').click(function () {
        signin();
    });

    // when the user clicks the "Sign Out" button
    $('#signout').click(function () {
        signout();
    });
    // when the user clicks the "Sign Out" button
    $('#logout-icon').click(function () {
        signout();
    });

    // toggle the logging panel
    $('#togglelogs').click(function () {
        $('#loggingdiv').toggle();
    });

    // toggle the logging panel
    $('#togglelegend').click(function () {
        $('#presencelegenddiv').toggle();
    });

    // toggling the settings panel
    $('#togglesettings').click(function () {
        $('#settings').toggle();
    });

    // clicked the about button
    $('#aboutlink').click(function () {
        $("#dialog").dialog("open");
        return false;
    });

    

    modalPosition();
    $(window).resize(function () {
        modalPosition();
    });
    $('#openModal').click(function (e) {
        $('.modal, .modal-backdrop').fadeIn('fast');
        e.preventDefault();
    });
    $('#aboutthedashboard').click(function (e) {
        $('.modal, .modal-backdrop').fadeIn('fast');
        e.preventDefault();
    });
    $('#modal-header-bar').click(function (e) {
        $('.modal, .modal-backdrop').fadeOut('fast');
    });
    $('.close-modal').click(function (e) {
        $('.modal, .modal-backdrop').fadeOut('fast');
    });
    function modalPosition() {
        var width = $('.modal').width();
        var pageWidth = $(window).width();
        var x = (pageWidth / 2) - (width / 2);
        $('.modal').css({ left: x + "px" });
        $('.modal').css({ top: "50px" });
    }
});

function clickAddress(name_id) {
    var id = name_ids.indexOf(name_id);
    log("Starting IM conversation with "+emailAddresses[id]);
    var emailAddress = emailAddresses[id];
    location.href = "sip:" + emailAddress;
}
/* 
 *
 * Matthew Proctor's Presence Dashboard
 * Functions to expand and collapse the left-side navbar
 * 
 */

(function () {
    $(function () {
        var collapseMyMenu, expandMyMenu, hideMenuTexts, showMenuTexts;
        expandMyMenu = function () {
            return $("nav.sidebar").removeClass("sidebar-menu-collapsed").addClass("sidebar-menu-expanded");
        };
        collapseMyMenu = function () {
            return $("nav.sidebar").removeClass("sidebar-menu-expanded").addClass("sidebar-menu-collapsed");
        };
        showMenuTexts = function () {
            return $("nav.sidebar ul a span.expanded-element").show();
        };
        hideMenuTexts = function () {
            return $("nav.sidebar ul a span.expanded-element").hide();
        };
        return $("#justify-icon").click(function (e) {
            if ($(this).parent("nav.sidebar").hasClass("sidebar-menu-collapsed")) {
                expandMyMenu();
                showMenuTexts();
                $(this).css({
                    color: "#000"
                });
            } else if ($(this).parent("nav.sidebar").hasClass("sidebar-menu-expanded")) {
                collapseMyMenu();
                hideMenuTexts();
                $(this).css({
                    color: "#FFF"
                });
            }
            return false;
        });
    });

}).call(this);
/* 
 *
 * Matthew Proctor's Presence Dashboard
 * Functions to sort the presence tiles - by name and status/presence 
 * 
 */

function sort_dashboard_by_status() {
    // Derived from http://stackoverflow.com/a/1603751/2276431
    var categories = new Array();
    var content = new Array();
    var ids = new Array();
    //Get Divs
    $('#dashboardoutput > [category]').each(function (i) {
        //Add to local array
        categories[i] = $(this).attr('category');
        ids[i] = $(this).attr('id');
        content[i] = $(this).html();
    });
    $('#dashboardoutput').empty();
    //Sort Divs
    for (i = 0; i < category_sort_order.length; i++) {
        //Grab all divs in this category and add them back to the form
        for (j = 0; j < categories.length; j++) {
            if (categories[j] == category_sort_order[i]) {
                $('#dashboardoutput').append('<div category="' + category_sort_order[i] + '" class="col-sm-3" id="' + ids[i] + '" >' + content[j] + '</div>');
            }
        };
    }
}


function sort_dashboard_by_name() {
    // Derived from http://stackoverflow.com/a/1603751/2276431
    var categories = new Array();
    var content = new Array();
    var ids = new Array();
    //Get Divs
    $('#dashboardoutput > [id]').each(function (i) {
        //Add to local array
        categories[i] = $(this).attr('category');
        ids[i] = $(this).attr('id');
        content[i] = $(this).html();
    });
    $('#dashboardoutput').empty();
    for (i = 0; i < ids.length - 1; i++) {
        for (j = i + 1; j < categories.length; j++) {
            if (ids[i] > ids[j]) {
                var temp_categories = categories[i];
                var temp_content = content[i];
                var temp_ids = ids[i];
                categories[i] = categories[j];
                content[i] = content[j];
                ids[i] = ids[j];
                categories[j] = temp_categories
                content[j] = temp_content;
                ids[j] = temp_ids;
            }
        }
    }
    //Sort Divs
    for (i = 0; i < ids.length; i++) {
        $('#dashboardoutput').append('<div category="' + categories[i] + '" class="col-sm-3" id="' + ids[i] + '" >' + content[i] + '</div>');
    }
}
body {
    font: 11pt calibri;
}

.container { padding-left:0px !important }

.input {
    border: 1pt solid gray;
    padding: 2pt;
    overflow: hidden;
    white-space: nowrap;
}

.button {
    border: 1pt solid gray;
    cursor: pointer;
    padding: 2pt 5pt;
    display: inline-block;
}

.settings, .presencelegenddiv {
    border: 1pt solid lightgray;
    padding: 5px;
    margin: 2px;
    width:800px;
}
.settingsbox {padding-top:10px; vertical-align:middle;}

.welcomebox {
    padding: 5px;
    margin: 2px;
    width:800px;
}

.alert {
    padding: 10px !important;
    margin-bottom: 5px !important;
    border-radius: 2px !important;
}

.button:hover {
    background: lightgray;
}

.signinframe {
    xpadding: 10pt 50px;
    width: 300px;
}

.layout {
    padding-left: 60px;
    padding-top: 10px;
}

.signinframe > div {
    margin-bottom: 3pt;
}

.signinframe > .button {
    margin-top: 8pt;
}



.modal-backdrop {
    background-color: rgba(128, 128, 128, 0.6) !important;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    display: none;
    z-index: 1000 !important;
}

.modal {
    width: 500px;
    height: 300px;
    position: absolute;
    top: 100px;
    z-index: 1020 !important;
    background-color: #FFF;
    border-radius: 6px;
    display: none;
}

.modal-header {
    background-color: #0094ff;
    color: #FFF;
    border-top-right-radius: 2px !important;
    border-top-left-radius: 2px !important;
}

.modal-header h3 {
    margin: 0;
    padding: 0 5px 0 5px;
    line-height: 20px;
}

.modal-header h3 .close-modal {
    float: right;
    text-decoration: none;
    color: #FFF;
}

.modal-body {
    padding: 0 10px 0 10px;
}


.spin {
     -webkit-transform-origin: 50% 58%;
     transform-origin:50% 50%;
     -ms-transform-origin:50% 50%; /* IE 9 */
     -webkit-animation: spin 1.5s infinite linear;
     -moz-animation: spin 1.5s infinite linear;
     -o-animation: spin 1.5s infinite linear;
     animation: spin 1.5s infinite linear;
}


@-moz-keyframes spin {
  from {
    -moz-transform: rotate(0deg);
  }
  to {
    -moz-transform: rotate(360deg);
  }
}

@-webkit-keyframes spin {
  from {
    -webkit-transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(360deg);
  }
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.lowheight {height:24px;}

.hiddendiv {
opacity: 0;
}
.hiddendiv:hover {
opacity: 1;
}

/* custom alert box for DND status */
.alert-dnd {
    color: #A94442;
    background-color: #F2DEDE;
    border-color: #ff0000;
    border-width: 2px;
}
body {
    background: none repeat scroll 0 0 white;
}

nav.sidebar-menu-collapsed {
    width: 50px;
}

nav.sidebar-menu-expanded {
    width: 106px;
}

nav.sidebar {
    position: fixed;
    top: 0px;
    left: 0px;
    height: 100%;
    background: none repeat scroll 0 0 #0094ff;
    color: white;
    padding: 20px 10px;
}

    nav.sidebar a#justify-icon {
        outline: 0;
        color: white;
        xfont-size: 16pt;
        xfont-style: normal;
    }

    nav.sidebar a#logout-icon {
        color: white;
        xfont-style: normal;
        left: 10px;
    }

    nav.sidebar ul {
        margin: 0;
        padding: 0;
        margin-top: 20px;
    }

        nav.sidebar ul li {
            margin: 0;
            padding: 0;
            margin-top: 20px;
            list-style-type: none;
        }

            nav.sidebar ul li a.expandable {
                outline: 0;
                color: white;
                text-decoration: none;
                font-size: 24pt;
            }

                nav.sidebar ul li a.expandable:hover {
                    color: #bbbbbb;
                }

                nav.sidebar ul li a.expandable span.expanded-element {
                    margin-left: 2px;
                    display: none;
                    xfont-size: 11px;
                    position: relative;
                    bottom: 2px;
                }

            nav.sidebar ul li.active {
                background: none repeat scroll 0 0 white;
                border-radius: 4px;
                text-align: center;
                margin-left: -4px;
                padding: 4px;
            }

                nav.sidebar ul li.active a.expandable {
                    color: white !important;
                }

                    nav.sidebar ul li.active a.expandable:hover {
                        color: white !important;
                    }

 

Very Important Considerations

The code examples here require the framework to be logged in as you (or using an account that has your contacts/groups configured as well) – so be careful with your credentials.

We recommend running this code on a site protected by SSL, and under no circumstances hard-coding your credentials into the code. If you do, it’s at your own peril!

 

 

Tags

Skype, Skype Web SDK, UCJA, JLync, UCWA, Dashboard
I've updated my Lync and Skype for Business Presence Dashboard application to improve usability, look & feel and add a few new options. The dashboard is open-source and available for download.
 
 

Popular Articles

Kilimanjaro 2015
Exploring Lync and IoT
Exchange 2013 in 60 minutes
Monitoring Lync with MRTG
Lync UCWA Tutorial - Introduction
Tutorial Parts 1 | 2 | 3 | 4 | 5

Recent Articles

Australian Postal Codes
Skype Web SDK
Using the Skype Web SDK from any language or framework
Extracting Email Addresses from Outlook Mailboxes using C#
Building a Skype for Business Auto Responder using the Skype Web SDK
Exporting Lync or Skype for Business Contacts with the Skype Web SDK

Favourite Links

Kutamo
Telco Together Foundation
Cloud on Kilimanjaro