User Profile Services via Client Object Model

I was recently thinking through ways to help drive adoption of an intranet and engage users to explore the various capabilities of the system. One thought was to alert users if they have not supplied a profile photo, or a short bio in their MySite. Sure we could do this with web services, but I thought I’d figure out how to do it with the client object model with ECMAScript. After a lot of searching, I kept finding blog posts that said I was out of luck and there was no way to query User Profiles via the client object model. At a high level, that assessment is correct, but what we can do is query the user info list of the current site.

When I first started this, none of the test users in my lab had a profile picture, so I added one and found that my script kept returning a null object instead of my picture. The hidden Easter egg here is that the site’s user info list isn’t updated in real time. If you head over to Central Administration -> Monitoring -> Review job definitions and dig down the list you’ll find a job called “User Profile Service Application – User Profile to SharePoint Full Synchronization“. This timer job will execute every hour and synchronize the properties of your User Profile Services to the site user info list, so the picture that was set on the MySite is now accessible via the user info list.

First things first, we need to find out the ID of the user that’s currently viewing the page (this is the physical numerical record of the user in the site’s user info list, not the login username). Credit to Mike Oryszak (@next_connect) and his blog post on using the status bar to display active workflows for getting me in the right direction on this piece. We’ll start out by grabbing the current web context and fire off that query. If the query is successful in executing, it’ll call the onUserSuccessMethod function.

var context = null;
var web = null;
var curUser = null;

function getUser() {
	context = new SP.ClientContext.get_current();
	web = context.get_web();
	curUser = web.get_currentUser();
	curUser.retrieve();
	context.load(web);
	context.executeQueryAsync(Function.createDelegate(this, this.onUserSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
}

Now that we have our context we’ll create a variable called user and assign it to the current user object, and call our loadProfile function to do the profile query.

function onUserSuccessMethod(sender, args) {
	var user = web.get_currentUser();
	loadProfile();
}

The loadProfile function defines the user info list, builds the CAML query to get the record for the current user, and fires that query off. If the query is successful in executing, it’ll call the onProfileSuccessMethod function.

function loadProfile() {
	context = SP.ClientContext.get_current();
	web = context.get_web();
	userInfoList = web.get_siteUserInfoList();
	camlQuery = new SP.CamlQuery();
	camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ID\'/><Value Type=\'Number\'>' + curUser.get_id() + '</Value></Eq></Where></Query><RowLimit>1</RowLimit></View>');
	this.listItems = userInfoList.getItems(camlQuery);
	context.load(listItems);
	context.executeQueryAsync(Function.createDelegate(this, this.onProfileSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
}

Now that we have the result set from our query (in the form of a listItems object), all we need to do is grab the first (and only item), this will be on index 0 in the array, and analyze the picture field to see if there’s a value there or not. If there is a picture object, we’ll grab the Url to it and save it in a new variable (pictureURL) just in case we want it. If there is no picture object, we’ll fire off a call to SP.UI.Status to add a new status bar telling the user they don’t have a photo, with a link to their profile where they can add one.

function onProfileSuccessMethod(sender, args) {
	var item = listItems.itemAt(0);
	var picture = item.get_item('Picture');
	if (picture) {
		var pictureURL = picture.get_url();
	} else {
		noPicture = SP.UI.Status.addStatus('Profile Photo', 'You have not added a profile photo to your account. <a href=\'http://mysites/person.aspx\'>Add one now!</a>');
		SP.UI.Status.setStatusPriColor(noPicture, 'blue');
	}
}

You’ll notice that both getUser() and loadProfile() have references to an onFailureMethod function, in the event that our query fails. This will be a simple function to just alert our error.

function onFailureMethod(sender, args) {
	alert('Error: ' + args.get_message() + '\n' + args.get_stackTrace());
}

Now that we’ve got all of our functions written, all we need is a simple call to our getUser() function (after the core SharePoint JavaScript has loaded, of course).

ExecuteOrDelayUntilScriptLoaded(getUser, "sp.js");

Putting it all together:

My “Michael Greene” account has a user profile image, so we just see normal SharePoint with no profile photo alerts.

My “Setup Account” account does not have a profile image, so SharePoint prompts us that we should add a photo.

Due to the fact that we have to wait on the timer job to run and update the user info list with the new photo, it’s possible that the user could add a photo then still see the prompt telling them they haven’t. In a true application of this, we could add a condition to check the Modified time of the user info list record to see if the record has been “updated” in the last hour, to avoid that false positive. Only if the record has been updated within the hour and has no photo, should we alert the user.


Update: June 13, 2012

Andrew made a great observation in the comment below, that the current user Id is available out of the box via the _spUserId JavaScript variable. This allows us to optimize this script by eliminating the call to the profile to get the current user’s Id. This revised script is much more efficient and should supersede what’s above. Thanks Andrew!

<script type="text/ecmascript" language="ecmascript">
	ExecuteOrDelayUntilScriptLoaded(loadProfile, "sp.js");
	
	var context = null;
	var web = null;
	
	function loadProfile() {
		context = SP.ClientContext.get_current();
		web = context.get_web();
		userInfoList = web.get_siteUserInfoList();
		camlQuery = new SP.CamlQuery();
		camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ID\'/><Value Type=\'Number\'>' + _spUserId + '</Value></Eq></Where></Query><RowLimit>1</RowLimit></View>');
		this.listItems = userInfoList.getItems(camlQuery);
		context.load(listItems);
		context.executeQueryAsync(Function.createDelegate(this, this.onProfileSuccessMethod), Function.createDelegate(this, this.onFailureMethod));
	}
	
	function onProfileSuccessMethod(sender, args) {
		var item = listItems.itemAt(0);
		var picture = item.get_item('Picture');
		if (picture) {
			var pictureURL = picture.get_url();
		} else {
			noPicture = SP.UI.Status.addStatus('Profile Photo', 'You have not added a profile photo to your account. <a href=\'http://mysites/person.aspx\'>Add one now!</a>');
			SP.UI.Status.setStatusPriColor(noPicture, 'blue');
		}
	}
	
	function onFailureMethod(sender, args) {
		alert('Error: ' + args.get_message() + '\n' + args.get_stackTrace());
	}
</script>

2 Responses to “User Profile Services via Client Object Model”

  1. Andrew says:

    Why are you making a call to the userinfolist for the current logged in user Id? This is already available via a variable _spUserId? You won’t have to load the whole user profile.

  2. Excellent observation Andrew- I’ve added an update to the post that uses the _spUserId variable as you suggested. Thanks for the feedback!

Leave a Reply