Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 3 – Create Display Templates
- Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 1 – Introduction
- Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 2 – Set up an Employee Directory Page
- Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 3 – Create Display Templates
- Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 4 – Search Schema
- Create a Simple SharePoint 2013 Employee Directory on Office365 – Part 5 – Sorting & Refiners
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:
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”.
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.
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.
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.
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
- 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:
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
Making 26 ajax calls to get the counts is definitely an overkill! btw it seems that you agree with me, based on your comments here ;-)
https://devspoint.wordpress.com/2010/11/15/improve-jquery-performance-by-using-asynchronous-ajax/
Until now, my preferred method to get such counts has been to create a collapsed grouped view, and make a single AJAX call to scrape the page. Not elegant, but by far the more efficient (especially on SP2010 and SP2013).
Of course now $batch is a game changer…
Ha! You dredged up a 3 year old comment. I definitely used to try to do stuff like this with DVWPs to keep it on the server.
I need to get the 26 calls into a $batch, I totally agree. I just haven’t gotten to it yet. It works as it is, but takes about .5s.
Since the series is about Office365, people ought to have or be getting $batch capabilities. Given the roll out schedules, at least this approach does the job. I just checked the Office365 roadmap, though, and “REST API Batching for Apps for SharePoint” should be “Generally available updates for all applicable customers”. That “applicable customers” bit *may* mean that not everyone reading the series will have the capability.
M.
3 years old, but that comment about SOAP services still holds true for REST! Let’s hope we are all “applicable”.
Another comment: the old version of REST services (listdata.svc) is also more efficient to get counts. Hopefully $count will soon be included in the 2013 REST API for “applicable customers”.
May be instead of calling the REST service 26 times. You can loop through and check each result returned in your control template itself and then may be highlight the respective alphabets. Haven’t really tried this, just a suggestion.
SC:
That would work great if we could retreive all of the people up front. Unfortunately, we’re capped at 50 items in any result set, so we dont have a view into all of the results client side.
M.
Hi Marc I notice the last column is “Office” but the data that is output is the Office Phone number. I have tried ctx.CurrentItem.BaseOfficeLocation and ctx.CurrentItem.Office in Item_Person_Table but no data is being output for “Office”. I have a value in the person’s Office location “Office” in their profile. But nothing is output in their “office ” column. What am I doing wrong or do you not retrieve the Office property.
Thanks Nigel
Nigel:
I’ve noticed that in a couple of tenants the office name was actually in a property called “OfficeNumber”. It makes little sense, but there you go.
The best thing to do it to take a look at the ctx object in the browser tools to see what values you have in the data. You may also have to change the properties you request in the Display Template.
M.
I’ve formatted my output in table format as described in Ari’s original post. I added a column for the user’s thumbnail photo. If I view the page, https://tenant.sharepoint.com/search/Pages/peopleresults.aspx, in Safari, the images show up. If I view the same page in IE11, the images are replaced with the dreaded X. Any ideas?
Hi Marc.
I followed your advices it seems and all users are resurned with the query. But certain fields that have data in the user profile are shown empty in the search results Item_result_table. It’s not consistent for all users, as some have the expected behavior, and data is missing for others. Any idea what this could be?
Alexandre:
The best way to debug this is to check in the admin pages to see what information is actually in the User Profiles and in what fields.
Also, the Display Templates I’ve given you here are as simple as I could make them for examples. It may be that you’ll need to do some escaping or something, especially if you’re using a language with characters not in English.
M.
Great blog but I need to know how to switch the Display Template as I did follow all steps in your blog and I cannot get it right to display your _Employee…..?
Hannu:
You switch the Display Templates in the setting for the Search Results Web Part. If you don’t see your Display Templates listed as options, it’s probably because you haven’t published them.
M.
Thanks Marc,
so I edit the page
select edit people search core results
select display templates under properties for search results
under display temp – results control disp temp i do see Default Result and People Search Table Control.
Am I suppose to see the _EmployeeDir here as well if so how do I publish the template as I am not an expert yet, still learning …. sorry :)
The People Search Table Control is the name of the template I show above. Choose that one.
M.
Awesome thanks Marc, great work as it is working 100%. Just miss this one step.
Marc, one more last question in the People Search Box it shows lasname suppose to Lastname as it does not found any names, where do I change the l to L?
If you’ve copied my code, it should be rigged up correctly. The link is constructed in line 31. Otherwise, I’m not sure of your question.
M.
in other words is it casecentive as well
Hi Marc,
I have change the Display Temp as you suggested but now when I click on A it stated “nothing here matches your search”. What I picked up is that it is all lower case like lastname:A* with the error when I change it to Lastname:A* it works?
im lying above it only work if you select People then it works
You’ve lost me. I’d suggest running back through the posts and seeing if you missed something.
M.