Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 3 – Create Display Templates

In the prior two parts of the series, I covered the idea of an employee directory (or associate directory, or person directory, or whatever you call the people in your company/organization/commune) and how to create the basic page. Once again, I want to give credit to Ari Bakker’s (@aribakker) post that shows how to set up a simple a Employee Directory on SharePoint 2013: How to: Create a Simple SharePoint 2013 People Directory.

I’m going to start with Ari’s Display Templates, but take them a bit further. What you’ll want to do here will vary based on the characteristics of your organization. What’s useful for an organization with 10 people will be quite different than what’s useful for one with 100k+ people.

The client I needed this directory for has about 100 employees. As with many organizations, the Employee Directory is effectively replacing something that has been maintained in Excel and emailed out regularly. Everyone prints it out and hangs up a copy in their cube. So we really want this directory to look a lot like that old-fashioned phone list. It will have a little more info and if the data maintenance side of things holds up, the data will always be current. At the same time, since we are using Display Templates, we’re well-positioned to expand the information we display over time.

In small- to medium-sized organizations like this, it’s helpful to have an alphabetic filter. I’ve been building similar things in SharePoint for a long time. (See: Alpha Selection of List Items in a Data View Web Part (DVWP)) They are great to add to plain old list views, too. I’ve created a Control Display Template which shows the alphabetical filtering links and an Item Display Template that shows each person’s details.

Here’s what the alphabetic filtering looks like:

Alphabetic Filtering

Note that several of the letters are not “lit up”. That’s because no one in the organization has a last name starting with I, Q, X, or Y. Few things are more annoying than clicking on a link like this only to be told that “Nothing here matches your search”.

Nothing here matches your search

If nothing matches, then why did you show me the link?!?!?! So there’s a little magic in the Control Display Template to figure out which letters should not be lit up. That logic will ensure that we only can click on letters where there are actual results, even as people come and go.

Like I said, there are two Display Templates here. These little buggers tend to work in pairs.

Display Template Layout
Source: http://msdn.microsoft.com/en-us/library/office/jj945138(v=office.15).aspx

Think of the Control template as the outer one and the Item template as the inner one which we iterate for every individual item in the result set coming back from search. This can be a little tricky and also a little confusing. Where should we draw that dotted line? Well, you’ll see a lot of inconsistency on this. In my two Display Templates for the Employee Directory, I’m using the Control template to display the alphabetic filter links and to create the table which will contain the items, but I’m rendering the table header in the Item template. That just seems to make more sense to me because that way the column headers sit with the rendering of the actual data, not is a separate place (the Control template). Another thing to consider here is that ideally we want the two types of Display Templates to work atomically: we should be able to mix and match different Control and Item templates based on our needs. For instance, in a really large organization, we may not need to check for which letter to light up, so we could just use a Control Template that doesn’t do that piece.

OK, enough chit-chat. Let’s look at the code.

I’ve created a folder in the Site Collection under _catalogs/masterpage to hold everything I’m doing here.

Employee Directory Code

In the real installation, that folder is named for the client, but here I’ve called it “_EmployeeDirectory”. Note the leading underscore: that ensures that the folder will always show up at the top of the listing under masterpage; otherwise I have to scroll a lot.

As you can see, I’m pretty organized about how I store things in the folder. I have subfolders for:

  • css – Any CSS files that are a part of this solution.
  • Display Templates – This folder structure mimics the one that SharePoint uses out of the box. I even mirror the subfolder names, like Search, so that it’s clear what type of Display Templates are in there. Because each of the Display Templates has a Content Type in it, SharePoint knows how to find the files in these custom folders.

Control Display Template If I had any custom JavaScript in the solution, I’d have a js folder, images files would go into an images folder, etc.

Here are the custom parts of the Control Display Template. The basic logic is this:

  • Line 3 – Include some custom CSS. In a full installation this would probably occur in the master page, but I’ve chucked it out to share the important bits here.
  • Line 4 – Include jQuery. I’m using jQuery to handle a bunch of things since it makes life easier.
  • Line 7 – This div is just the outer container for the template.
  • Lines 10-15 – Declare some variables we’ll need later
  • Lines 19-36 – Emits the markup for the alphabetic filters.
  • Lines 22-28 – Loop through all of the letters in the alphabet and make the calls to search to find out if that letter should be “lit up”.
  • Line 31 – Emit the hard-wired “All” link.
  • Lines 38-53 – This block is where I figure out which letters to unlight change the CSS for each. Yes, After fiddling around with this for a while, I decided to load the page with *all* the letter lit up, and turn off the ones that don’t have data behind them. This makes for a good regression if we have one: all the links will be lit up even if the script fails.
  • Lines 55-76 – This function called getSearchResultsUsingREST makes a call to the Search Service using REST and passes back a promise. Each call requests just the first result (rowlimit=1) and only the WorkId property (selectproperties=’WorkId’). This makes the call extremely “light”. We don’t need to know how many people fall into the letters bucket; we just want to know if *any* do.
<body>
	<script>
		$includeCSS(this.url, "~sitecollection/_catalogs/masterpage/_EmployeeDirectory/css/EmployeeDirectory.css");
		$includeLanguageScript(this.url, location.protocol + "//code.jquery.com/jquery-1.11.2.min.js");
	</script>

    <div id="Control_SearchResults">

<!--#_
		// Make REST calls to the Search API to "light up" the letters.
		var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		var letters = alphabet.split("");
		var lettersSearch = []; // Array to hold the AJAX promises
		var currentSearch = (location.href.indexOf("k=lastname%3A") > 0) ? location.href.split("k=lastname%3A")[1].substr(0, 1) : "";
		var thisPage = location.pathname;
_#-->                    

		<!-- Alpha selector container -->
		<div id="edir-alpha-links">
			<span><a href=_#=thisPage=#_>All</a></span>
<!--#_
		// Loop through the alphabet and make a call to the search API for each starting letter
		for(var i=0; i < letters.length; i++) {
			var searchTerm = letters[i];
			var letterClass = (currentSearch === searchTerm) ? "selected" : "";		

			// TODO Build the calls into one REST $batch. See: http://www.andrewconnell.com/blog/part-1-sharepoint-rest-api-batching-understanding-batching-requests
			lettersSearch[i] = getSearchResultsUsingREST("LastName%3A" + searchTerm + "*");
_#-->
			<span id="edir-alpha-links-_#= searchTerm =#_">
				<a class="_#=letterClass=#_" href="_#= location.pathname =#_#k=lastname%3A_#= searchTerm =#_*">_#= searchTerm =#_</a>
			</span>
<!--#_
		}
_#-->
		</div>
<!--#_
		// When all the requests have completed...
		$.when.apply($, lettersSearch).done(function() {

			for(var i=0; i < letters.length; i++) {

				var searchTerm = letters[i];
				var count = this[i];

				var letter = $("#edir-alpha-links-" + letters[i]);
				if(count == 0) {
					letter.html(searchTerm);
					letter.addClass("no-link");
				};
			}

		});

		function getSearchResultsUsingREST(queryText) {

			var result = new $.Deferred();
			var resultCount = 0;

			// We only need to fetch the first result for each letter to know if we should light it up
			// The sourceId is the GUID for the 'Local People Results' Result Source
			var searchUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?querytext='" + queryText + "'&sourceId=%27b09a7990-05ea-4af9-81ef-edfab16c4e31%27&rowlimit=1&selectproperties='WorkId'";

			var p = $.ajax({
				url: searchUrl,
				method: "GET",
				headers: { "Accept": "application/json; odata=verbose" }
			});

			p.done(function(data) {
				// Return just the count of results, which will be 0 or 1
				result.resolveWith(data.d.query.PrimaryQueryResult.RelevantResults.RowCount);
			});

			return result.promise();
		}

_#-->

Next up is the Item Display Template. In this one, I’ve gone as simple as possible. Ive gotten rid of a lot of the encoding, null value tests, etc. that most of the out-of-the-box Displey Templates have just to keep it simple. Below I’m showing *everything* in the body of the template. It’s really stripped down.

 

  • Line 9-21 – If this is the first item in the result set, emit the table header.
  • Line 23-33 – Emit the details for each person in the rsult set. Here I’m showing:
    • Name (full name)
    • Work Phone
    • Email
    • Department
    • Office

Your organization will undoubtedly have a few other fields you want to add, you may one to remove one or two of these, etc.

<body>
    <div id="Item_Person">
<!--#_
		if(!$isNull(ctx.CurrentItem) && !$isNull(ctx.ClientControl)){
			var encodedPath = $urlHtmlEncode(ctx.CurrentItem.Path);
_#-->
<!--#_
			// If this is the first item in the results, emit the table header
			if(ctx.CurrentItemIdx === 0) {
_#-->
				<thead>
					<tr class="ms-viewheadertr ms-vhltr">
						<td class="ms-vh2">Name</td>
						<td class="ms-vh2">Work Phone</td>
						<td class="ms-vh2">Email</td>
						<td class="ms-vh2">Department</td>
						<td class="ms-vh2">Office</td>
					</tr>
				</thead>
<!--#_
			}
_#-->
			<tr>
				<td class="ms-vb2">
					<a clicktype="Result" id="NameFieldLink" href="_#= encodedPath =#_" title="_#= ctx.CurrentItem.PreferredName =#_">_#= ctx.CurrentItem.PreferredName =#_</a>
				</td>
				<td class="ms-vb2">_#=ctx.CurrentItem.WorkPhone=#_</td>
				<td class="ms-vb2">
					<a href="mailto:_#=ctx.CurrentItem.WorkEmail=#_">_#=ctx.CurrentItem.WorkEmail=#_</a>
				</td>
				<td class="ms-vb2">_#= ctx.CurrentItem.Department =#_</td>
				<td class="ms-vb2">_#= ctx.CurrentItem.OfficeNumber =#_</td>
			</tr>
<!--#_
	}
_#-->
    </div>
</body>

Finally, here’s the CSS I included above:

/* Alpha links for search */
#edir-alpha-links {
	margin-top:10px;
}
#edir-alpha-links span {
	padding:0 3px 0 3px;
	width:35px;
	font-size:18px;
}
#edir-alpha-links span.no-link {
	padding:0 6px 0 6px;
}
#edir-alpha-links span a {
	padding:0 3px 0 3px;
	border-radius:6px 6px 6px 6px;
	background-color:#005581;
	color:#fff;
}
#edir-alpha-links span a.selected {
	background-color:#E08B24;
}
/* Alpha links for search */

Please don’t use this as-is. I’m so disappointed when I see one of my examples shows up somewhere with exactly the same colors and fonts!

Once we’ve switched to these Display Templates, we have something like this:
Employee Directory - Part 3

You can see we still have a few oddities here. This screen grab is from my demo environment, so there aren’t that many real people in the results. (Note that only a few of the letters are lit.) We’re also seeing some odd “people” that aren’t people at all. There’s probably some other filtering we’d like to do, maybe a few more settings to tweak. Next up I’ll show you some of the things you’ll probably want to do with your Search Schema to make things work a little better. WARNING: Powershell is coming. Sad, but true.

Here are the files from this post in a ZIP file: _EmployeeDirectory Display Templates and CSS

Series Navigation<< Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 2 – Set up an Employee Directory PageCreate a Simple SharePoint 2013 Employee Directory on Office365 – Part 4 – Search Schema >>

Similar Posts

46 Comments

  1. This is a great article but I am having an issue with adding MobilePhone property. I am not sure what I am doing wrong. I followed lines 23 -33 is there something else you need to do?

  2. Thanks for the great information. I’m fairly new to SharePoint and learning.
    I’m first copying this blog to learn and will try to make some modification as needed.

    While everything worked as expected, it’s not showing up as list(or table) like how yours show.

    I do have both templates and css loaded, and Control Display template portion and css seems to be showing. Only the Item Display template doesn’t seem to be working.

    Could you advise what I might be doing incorrectly?

    1. @wphoil:

      It’s really hard to say what the issue is. I’d suggest doing some debugging in the developer tools to see if you’ve got an error in your JavaScript.

      M.

  3. Hi,
    I have customized Employee directory and the paging is working fine on but it is not working as expected on IE when we open the page first time paging is not showing correct, it does not load all the results first time but when we do the control F5 it start working.

  4. When I tried to connect the web part to uploaded display form I have received an error:

    One or more of the following resource files failed to load: •http://code.jquery.com/jquery-1.12.1.min.js

    Display Error: The display template had an error. You can correct it by fixing the template or by changing the display template used in either the Web Part properties or Result Types.

    ‘$’ is undefined (CoreRender: ~sitecollection/_catalogs/masterpage/_EmployeeDirectory/Display Templates/Search/Control_SearchResults_Table.js)

    I have uploaded the jquery-1.12.1.min.js directly to the site and updated the link in the Control_SearchResults_Table file, but it doesn’t help.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.