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. Hi Marc I have an interesting problem and I don’t know if it is you or Search, so I will start with you. In the “Change Query” on the people search results page. “Build your query” If I add contentclass=spspeople then the “All” button works, but not the letters A-Z. If I take it out contentclass=spspeople then the letters A-Z in your display template work, but not “All”. Any Ideas ?
    Thanks

    Nigel

        1. So no errors, just not the results you expect. I’m not sure why that is happening. Do you have a space between {searchboxquery} and contentclass=spspeople? That tripped up Johnathan above.

          M.

          1. No if I put (searchquerybox}contentclass=spspeople “All” works but LastName:A* does not return results. If I leave out contentclass=spspeople all together then “All” returns no results but Lastname:A* does I might just take out the “All” from the Display Template. After all 2,500 users is a bit too many to get on one or two pages !

  2. I am experiencing the same problem Hannu reported. I changed the Control and Item Display Templates for the web part. The A-Z list displays and the results display in a table on my page. When I click any of the alpha links, I receive a message that “Nothing here matches your search”. I used the files available in this post and did not make any changes. Let me know if you have any ideas on how to make this work. Thank you.

  3. I still cannot get the Office field in. I’ve tried ‘OfficeNumber’ as suggested and no matter what I do, ctx.CurrentItem never returns an Office field. I can type “OfficeNumber:Vinings” for example and get the correct results, but I cannot get the office field in the Item_Person_Table.html. Any help would be greatly appreciated.

    1. Anthony:

      There seems to be some variation on this, and I’m not sure what the pattern is. If you go into the admin page for the Search Schema (/_layouts/15/searchadmin/ta_listmanagedproperties.aspx?level=tenant) and search for “Office”, you’ll see a number of different columns. In my tenant, the OfficeNumber managed property is mapped to People:Office and is the right one to use. You may have a different managed property for Office.

      However, given that you can get results by typing “OfficeNumber:Vinings”, you may want to check the mso:ManagedPropertyMapping element in the control template to be sure you are retrieving the OfficeNumber managed property.

      M.

  4. Marc, if I were to add some jquery to this, would I be editing the Item_Person_Table.html page? Given that the other template, would I need to link to the jquery source again or is it inherited? how does SP handle that? Have this up and now it’s down to fine tuning. Probably one of the best Blogs out there for this subject. =) Thanks

  5. Marc- Great stuff. I am a complete SP newbie. Followed your instructions, double and triple checked. A question or two though:
    1- Where do the .js files come from? You have sample html and css files but I don’t see any .js file samples. I don’t have the first clue how to code js.

    2- I read the thread above with Hannu Swanepoel over and over but just can’t seem to get anything but the Default Result Display Template to show up under Display Templates > Results Control Display Template. I have the files published.

    Any thoughts?

  6. Hi Marc,
    Couple of things. I don’t get data in Office, OfficeLocation, BaseOfficeLocation, OfficeNumber – none of them. I even created my own OfficeLocationSearch managed metadata field pulling from SPS:Location and that doesn’t have any data in it either… So thoughts on that one.
    Question two: my “ALL” results come back with 320 names but only 5 letters are lite up in the header. If I do a manual search for lastname:R I don’t get any results but there are people that display in my “ALL” that have a last name of R. Now that could be because they have not logged into SharePoint and their Profile is not fully populated. So to get around this and only display the people in “ALL” that actually have a lite up letter I want to narrow my search criteria but I can’t figure out how to modify the search query to something like this: {searchboxquery} contentclass=spspeople !isNull(LastName). I’ve tired adding LastName:a* Lastname:b* etc… and that doesn’t pull back all the results either. so how to match the inner template search results with the outer template search results that only queues off of LastName?

    1. Mike:

      What data is in each field is most likely driven from the sync with Active Directory. Every situation seems a bit different. Make sure that you can see values in the User Profiles themselves for Office. In the User Profile page, you can inspect the DOM to determine which field holds the data.

      Question two is a little trickier. Do you see all 320 when “All” is selected? (You’ll have to page.) If so, there might be something wrong in your script.

      M.

  7. Hi, Marc. I’ve successfully implemented this (it works great!) and expanded upon it to include an alphabetical filter by first name, which also works perfectly. The part I can’t figure out is how to do the same thing for first names’ alphabetical filter that’s been done for the last names, i.e., how to make the letters in that list go dark if no one’s first name starts with that letter. Nothing I’ve tried so far has worked. Any advice? Thanks.

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.