Permalink
Browse files

Added ability to lookup users

Includes a jQuery extension called userlookup that provides SharePoint-like ctrl+k behaviour
  • Loading branch information...
robdmoore committed Nov 4, 2014
1 parent 7d5b7c2 commit 1e8b673bdcbd4327d5de64d641d03411d06f26bd
@@ -19,11 +19,19 @@ public static void RegisterBundles(BundleCollection bundles)
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js"));
+ bundles.Add(new ScriptBundle("~/bundles/userlookup").Include(
+ "~/Scripts/typeahead.jquery.js",
+ "~/Scripts/userlookup.js"));
+
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.css",
- "~/Content/site.css"));
+ "~/Content/site.css",
+ "~/Content/typeaheadjs.css",
+ "~/Content/userlookup.css"));
- BundleTable.EnableOptimizations = true;
+ #if !DEBUG
+ BundleTable.EnableOptimizations = true;
+ #endif
}
}
}
@@ -159,6 +159,7 @@
<Compile Include="App_Start\Startup.cs" />
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Controllers\LogoutController.cs" />
+ <Compile Include="Controllers\UserLookupController.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
@@ -174,6 +175,9 @@
<Content Include="Content\bootstrap-theme.min.css" />
<Content Include="Content\bootstrap.css" />
<Content Include="Content\bootstrap.min.css" />
+ <Content Include="Content\loading.gif" />
+ <Content Include="Content\typeaheadjs.css" />
+ <Content Include="Content\userlookup.css" />
<Content Include="favicon.ico" />
<Content Include="fonts\glyphicons-halflings-regular.svg" />
<Content Include="Global.asax" />
@@ -199,6 +203,8 @@
<Content Include="Scripts\respond.matchmedia.addListener.js" />
<Content Include="Scripts\respond.matchmedia.addListener.min.js" />
<Content Include="Scripts\respond.min.js" />
+ <Content Include="Scripts\typeahead.jquery.js" />
+ <Content Include="Scripts\userlookup.js" />
<Content Include="Scripts\_references.js" />
<Content Include="Web.config" />
<Content Include="Web.Release.config">
@@ -212,6 +218,7 @@
<Content Include="Views\Home\Group1.cshtml" />
<Content Include="Views\Home\Group2.cshtml" />
<Content Include="Views\Home\Group3.cshtml" />
+ <Content Include="Views\UserLookup\Index.cshtml" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
@@ -2,16 +2,8 @@
padding-top: 50px;
padding-bottom: 20px;
}
-
-/* Set padding to keep content from hitting the edges */
.body-content {
padding-left: 15px;
padding-right: 15px;
}
-/* Set width on the form input elements since they're 100% wide by default */
-input,
-select,
-textarea {
- max-width: 280px;
-}
Binary file not shown.
@@ -0,0 +1,57 @@
+span.twitter-typeahead .tt-dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ list-style: none;
+ font-size: 14px;
+ text-align: left;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ background-clip: padding-box;
+}
+span.twitter-typeahead .tt-suggestion > p {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 1.42857143;
+ color: #333333;
+ white-space: nowrap;
+}
+span.twitter-typeahead .tt-suggestion > p:hover,
+span.twitter-typeahead .tt-suggestion > p:focus {
+ color: #ffffff;
+ text-decoration: none;
+ outline: 0;
+ background-color: #428bca;
+}
+span.twitter-typeahead .tt-suggestion.tt-cursor {
+ color: #ffffff;
+ background-color: #428bca;
+}
+span.twitter-typeahead {
+ width: 100%;
+}
+.input-group span.twitter-typeahead {
+ display: block !important;
+}
+.input-group span.twitter-typeahead .tt-dropdown-menu {
+ top: 32px !important;
+}
+.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
+ top: 44px !important;
+}
+.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
+ top: 28px !important;
+}
+
@@ -0,0 +1,25 @@
+.loading {
+ background-image: url("loading.gif");
+ background-repeat: no-repeat;
+ background-position: 100% 50%;
+}
+
+.matchfound {
+ text-decoration: underline;
+}
+
+.nomatchfound {
+ color: red;
+ text-decoration: underline;
+ font-style: italic;
+ -moz-text-decoration-line: underline;
+ -moz-text-decoration-color: red;
+ -moz-text-decoration-style: wavy;
+ -webkit-text-decoration-line: underline;
+ -webkit-text-decoration-color: red;
+ -webkit-text-decoration-style: dotted;
+ -webkit-text-decoration-style: wavy;
+ text-decoration-line: underline;
+ text-decoration-color: red;
+ text-decoration-style: wavy;
+}
@@ -0,0 +1,73 @@
+using System;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Configuration;
+using System.Web.Mvc;
+using AzureAdMvcExample.Infrastructure.Auth;
+using Microsoft.Azure.ActiveDirectory.GraphClient;
+
+namespace AzureAdMvcExample.Controllers
+{
+ public class UserLookupController : Controller
+ {
+ private readonly AzureADGraphConnection _graphConnection;
+
+ public UserLookupController()
+ {
+ // This should be injected in via a DI container, but for simplicity of the demo I'll leave it as a new statement
+ _graphConnection = new AzureADGraphConnection(
+ ConfigurationManager.AppSettings["AzureADTenant"],
+ ConfigurationManager.AppSettings["ida:ClientId"],
+ ConfigurationManager.AppSettings["ida:Password"]);
+ }
+
+ public ActionResult Index()
+ {
+ return View(new UserLookupViewModel());
+ }
+
+ [HttpPost]
+ public ActionResult Index(UserLookupViewModel vm)
+ {
+ if (ModelState.IsValid)
+ vm.User = _graphConnection.GetUser(vm.UserId.Value);
+ else
+ vm.UserId = null;
+
+ return View(vm);
+ }
+
+ public ActionResult Search(string q)
+ {
+ var users = _graphConnection.SearchUsers(q);
+
+ return Json(users, JsonRequestBehavior.AllowGet);
+ }
+ }
+
+ public class UserLookupViewModel
+ {
+ private User _user;
+
+ [Required]
+ public Guid? UserId { get; set; }
+
+ [Required]
+ public string UserName { get; set; }
+
+ [ReadOnly(true)]
+ public User User
+ {
+ get
+ {
+ return _user;
+ }
+ set
+ {
+ _user = value;
+ UserId = Guid.Parse(_user.ObjectId);
+ UserName = _user.DisplayName;
+ }
+ }
+ }
+}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Linq.Expressions;
using System.Security.Claims;
using Humanizer;
using Microsoft.Azure.ActiveDirectory.GraphClient;
@@ -11,6 +12,8 @@ namespace AzureAdMvcExample.Infrastructure.Auth
public interface IAzureADGraphConnection
{
IList<AppRoles> GetRolesForUser(ClaimsPrincipal userPrincipal);
+ IList<User> SearchUsers(string query);
+ User GetUser(Guid id);
}
public class AzureADGraphConnection : IAzureADGraphConnection
@@ -39,5 +42,44 @@ public IList<AppRoles> GetRolesForUser(ClaimsPrincipal userPrincipal)
.Select(r => r.Value)
.ToList();
}
+
+ public IList<User> SearchUsers(string query)
+ {
+ var displayNameFilter = ExpressionHelper.CreateStartsWithExpression(typeof(User), GraphProperty.DisplayName, query);
+ var surnameFilter = ExpressionHelper.CreateStartsWithExpression(typeof(User), GraphProperty.Surname, query);
+ var usersByDisplayName = _graphConnection
+ .List<User>(null, new FilterGenerator { QueryFilter = displayNameFilter })
+ .Results;
+ var usersBySurname = _graphConnection
+ .List<User>(null, new FilterGenerator { QueryFilter = surnameFilter })
+ .Results;
+
+ return usersByDisplayName.Union(usersBySurname, new UserComparer()).ToArray();
+ }
+
+ public User GetUser(Guid id)
+ {
+ try
+ {
+ return _graphConnection.Get<User>(id.ToString());
+ }
+ catch (ObjectNotFoundException)
+ {
+ return null;
+ }
+ }
+
+ class UserComparer : IEqualityComparer<User>
+ {
+ public bool Equals(User x, User y)
+ {
+ return x.ObjectId == y.ObjectId;
+ }
+
+ public int GetHashCode(User obj)
+ {
+ return obj.ObjectId.GetHashCode();
+ }
+ }
}
}
@@ -1,7 +1,11 @@
-/// <reference path="jquery-2.1.1.js" />
+/// <autosync enabled="true" />
+/// <reference path="jquery-2.1.1.js" />
/// <reference path="modernizr-2.8.3.js" />
-/// <autosync enabled="true" />
-਍⼀⼀⼀ 㰀爀攀昀攀爀攀渀挀攀 瀀愀琀栀㴀∀洀漀搀攀爀渀椀稀爀ⴀ㈀⸀㘀⸀㈀⸀樀猀∀ ⼀㸀ഀഀ
-਍⼀⼀⼀ 㰀爀攀昀攀爀攀渀挀攀 瀀愀琀栀㴀∀戀漀漀琀猀琀爀愀瀀⸀樀猀∀ ⼀㸀ഀഀ
+/// <reference path="bootstrap.js" />
+/// <reference path="jquery.validate.js" />
+/// <reference path="jquery.validate.unobtrusive.js" />
+/// <reference path="respond.js" />
+/// <reference path="respond.matchmedia.addlistener.js" />
+/// <reference path="userlookup.js" />
/// <reference path="respond.js" />
Oops, something went wrong.

0 comments on commit 1e8b673

Please sign in to comment.