SharePoint Branding Best Bets (SPSRIC2013)

Diving into the world of SharePoint branding can be a real headache. What is the best practice for deploying my branding? How do I select a design firm? What is the impact of mobile devices and how do I ensure cross-browser compatibility? What are the new branding tools available in SharePoint 2013? These are all common questions that must be answered during the course of branding efforts. In this session we’ll look at the various aspects of SharePoint branding, and common pitfalls to look out for during your next branding project.

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>

Enhancing the SharePoint 2010 UI: Scripted Orientation Aware Content

In my previous post we looked at enhancing the SharePoint 2010 UI through CSS based orientation detection. In this post, we will take it one step further and use some client side script to detect orientation and output content accordingly.

The CSS approach utilizes orientation aware style sheets which are loaded based on the appropriate orientation of the device (in this case an iPad). While this approach is simple to implement, it's not necessarily the most robust solution or the most scalable solution. The CSS approach is best suited to deployment with your site's branded master page, when orientation detection is a big part of your design. What if you only wanted it on one or two pages of your site, if you don't have access to deploy a new master page, or if you want more advanced orientation detection?

An alternative approach is to use client side scripting to detect the device orientation and process contextual changes for specific orientations. Another advantage to a scripted approach is that we now have some more information from the browser. The CSS approach simply looks for portrait vs. landscape, while the scripted approach gives us an integer value of the device orientation (0, 90, 180, or -90). Note that the browser gives us a value of "-90" as opposed to the "270" you'd expect to see when talking about rotation.

So to get started I'm going to create two div's in my design, one with an ID of "OrientationAlert" which we'll use to tell the user that more content is available if they rotate the device, and another with an ID of "SampleContent" to contain the demo content.

HTML:

<div id="OrientationAlert">Rotate device to landscape to view additional content.</div>
<div id="SampleContent">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean rutrum ligula sed diam porta eleifend vitae sodales nisl. Nam hendrerit sodales mattis. Vestibulum dictum, erat sit amet sodales dignissim, libero est vulputate sem, sit amet mollis justo sapien mattis dolor. In semper velit ut urna luctus pharetra. Cras pellentesque enim in massa laoreet mattis. Ut consequat, mi quis tempor ultrices, velit urna vestibulum metus, sed tempor nisl magna quis tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse id eros turpis, quis faucibus quam. Donec tempus pretium mollis. In posuere, magna sit amet facilisis porttitor, lorem libero pretium libero, ut tempus neque tortor vitae eros. Proin nec neque sapien, vel convallis nisl. Donec ut nulla at ligula semper laoreet sed quis metus. Vivamus pretium turpis sit amet nibh laoreet non interdum dui accumsan. Fusce mollis, velit eget lobortis vestibulum, metus est malesuada justo, ac tempus est eros et purus. Aliquam non leo nisl, nec viverra elit.</div>

Next we'll put in some Javascript to do the work. Note that we're also calling jQuery to simplify things; simply put the jQuery library somewhere in your site and link to the appropriate path.

Javascript:

<script type="text/javascript" src="/Style%20Library/jquery-1.4.2.min.js"></script><script type="text/javascript">// <![CDATA[
    // Boolean; is device an iPad or not
    var isiPad = navigator.userAgent.match(/iPad/i) != null;
    if (isiPad) { // Process only for iPads
        ProcessOrientation(window.orientation); // Process initial orientation
        window.onorientationchange = function() { // Process on orientation change
            ProcessOrientation(window.orientation);
        }

        function ProcessOrientation(curOrientation) {
            if (curOrientation == 0 || curOrientation == 180) {
                // Portrait Orientation
                $("#OrientationAlert").show();
                $("#SampleContent").hide();
            } else if (curOrientation == 90 || curOrientation == -90) {
                // Landscape Orientation
                $("#OrientationAlert").hide();
                $("#SampleContent").show();
            }
        }
    } else {
        $("#OrientationAlert").hide();
    }
// ]]></script>

So what exactly does this do? First things first, if you think back to the previous blog post, you'll remember that we had to do some HTML "if" statements to apply the right style sheets to avoid confusing IE. In this scenario, we're doing it differently and only applying the code to the iPad. Line 4 in the code block above scans the user agent string from the browser, looking for "iPad". It then sets the isiPad variable to true if "iPad" is found, or false. We then analyse that variable (at line 5) so that we can do certain things if we're on an iPad, and others if we're not.

In this scenario we're going to hide the sample content in portrait, and show it in landscape. Likewise, if that content is hidden, we're going to show the user the OrientationAlert text so they know there's more available to see if they turn the device. Finally, if they're on a PC (the "not iPad"/else part of the equation) we're just going to hide the user alert since they'll be seeing all of the content.

You'll notice that there's a function in there called "ProcessOrientation" that contains the nuts and bolts of what we're doing. Before we dig into that we need to understand how it gets called. Lines 6 through 9 do a couple things. First things first, we have to call "ProcessOrientation" as soon as the script is loaded so that we determine the initial orientation. We then create a function that calls "ProcessOrientation" and attach it to the window.onorientationchange event. This tells the browser to run "ProcessOrientation" every time the device orientation is changed.

Now onto ProcessOrientation()--this is the function that does the work. For the purposes of this post, I've combined the landscape and portrait modes (0 and 180, 90 and -90), but you could certainly do different things based on each possibility. We then simply use jQuery hide() and show() to either hide the content and show the instruction alert, or hide the instruction alert and show the content.

With this approach, the orientation of the device really can become an integral component to the user interface and navigation. As an example, you could show different list views based on if the device is rotated to the left or to the right.

Another important thing to note is that this solution can be deployed in a number of ways, such as directly including it in the markup of a masterpage, layout, or individual page, or by including with a content editor web part. So for those of you playing in a Tier1 environment where you can't deploy solutions, you can technically roll out a solution like this through the web GUI.

Enhancing the SharePoint 2010 UI: iPad Orientation Detection

I recently had a chance to sit down and rapid prototype some iPad UI Enhancements for SharePoint 2010. I had previously done some light orientation detection for the iPod and iPhone, but with the iPad there's a lot more power in utilizing orientation detection. As the iPad gains more traction in the business world I expect to see more and more requirements to make enterprise platforms [like SharePoint] usable on the iPad. With Microsoft's commitment to cross-browser functionality in SharePoint 2010 we're fortunate that SharePoint 2010 will render on the iPad without any customizations needed.

The typical approach to porting desktop content to a mobile device is to scale content down to fit it all within the mobile device's display. With orientation detection we can turn off specific regions of content, or change the layout completely in order to maximize on the screen real estate and mitigate the need to scale down content--relative to how the user is holding the device. If we use the iPad as an example, in landscape orientation the resolution is 1024 x 768 but when we move it to portrait orientation the resolution swaps to 768 x 1024 (~250 pixels less on the width). Again, typically we'd just scale everything down to compensate for that drop on horizontal screen real estate. With a platform like SharePoint, making things smaller inherently makes it more difficult to navigate and the two-finger horizontal scroll on the iPad isn't really all that intuitive. The solution is to detect the orientation of the device and restructure content accordingly.

Orientation Based Style Sheets
At the core of these examples is orientation based style sheets. The iPad allows us to attach style sheets to only be used for the listed orientation. To attach orientation aware style sheets, append the orientation to the media attribute.

HTML:

This method will apply the iPadPortrait.css style sheet when the device is portrait, and the iPadLandscape.css style sheet when the device is landscape. Make sure you put your style sheet link tags after the core SharePoint style sheets to ensure they override the out of the box styling. Modern browsers like Chrome will detect the orientation parameter and compare the window width to the height to determine which one to use; Internet Explorer will not do this, but rather add both of your style sheets. In order to ensure cross-platform compatibility we need to do a simple browser check so that we don't show IE both style sheets.

HTML:

<!--[if !IE]><!-->

<!--<![endif]-->
<!--[if IE]>
          <link rel="stylesheet" href="/Style%20Library/iPadLandscape.css" />
<![endif]-->
Hiding the Quick Launch in Portrait If all you want to do is hide content in a specific orientation, then you only need a style sheet for that orientation (vs. one for each). Styles in the loaded style sheet can override styles defined in the core css. For example, if you wanted to hide the quick launch in portrait mode you would simply load a portrait style sheet containing CSS to hide the quick launch bar. HTML:
#s4-leftpanel {
  display: none;
}
.s4-ca {
  margin-left: 0px;
}
**Changing Layout** If you want to "move" content based on orientation then you'll need two style sheets (one for each orientation), and a little bit of thinking on your layout. For this example we will place a navigation bar on the left of the screen in landscape mode, and make it in-line across the top of the screen in portrait mode. First things first, when creating your layout you'll need to make two navigation bars (one where you want it to be in landscape, the other where you want it in portrait). As an example, if you wanted one in a left column in landscape and one across the top in portrait you could do something like the following (including the OOTB SharePoint markup).
<table width="100%" border="0" cellspacing="0" cellpadding="4">
<tbody>
<tr>
<td id="_invisibleIfEmpty" class="iPadPortrait" colspan="2" valign="top" width="100%">
<ul class="iPadNavigation">
    <li>Chart 1</li>
    <li>Chart 2</li>
    <li>Chart 3</li>
    <li>Chart 4</li>
    <li>Chart 5</li>
    <li>Chart 6</li>
</ul>
</td>
</tr>
<tr>
<td id="_invisibleIfEmpty" class="iPadLandscape" valign="top" width="200" height="100%">
<ul class="iPadNavigation">
    <li>Chart 1</li>
    <li>Chart 2</li>
    <li>Chart 3</li>
    <li>Chart 4</li>
    <li>Chart 5</li>
    <li>Chart 6</li>
</ul>
</td>
<td id="_invisibleIfEmpty" valign="top" width="100%" height="100%">Standard SharePoint Zone</td>
</tr>
</tbody>
</table>

Obviously it's a bit more work to create two navs, but if you're writing the markup through script or through a DVWP then the additional work is negligible. You'll notice that the markup for the two is identical, the only change being that the parent cell has a class of either iPadPortrait or iPadLandscape. This is done intentionally so that we can re-use styling between the two navs; this ensures that you don't have to manage baseline button looks in two places.

.iPadPortrait .iPadNavigation, .iPadLandscape .iPadNavigation {
  width: 100%;
  padding: 0;
  margin: 0;
  text-align: center;
  list-style-type: none;
}
.iPadPortrait .iPadNavigation li, .iPadLandscape .iPadNavigation li {
  margin: 4px;
  border: 1px solid #333;
  font-weight: bold;
  padding: 8px 0;
  font-size: 12px;
}

In our individual orientation style sheets we then add the appropriate CSS to hide the elements used in the opposite orientation, and make any changes to the base button styles. Note the addition of the display parameter on the portrait navigation CSS to make our button elements display inline vs. stacked.

iPadPortrait.css:

.iPadLandscape {
    display: none; /*hides all "Landscape" classes*/
}
.iPadPortrait .iPadNavigation li {
  display: inline-block;
  width: 130px;
}

iPadLandscape.css:

.iPadPortrait {
    display: none; /*hides all "Portrait" classes*/
}
.iPadLandscape .iPadNavigation li {
  width: 150px;
}

Site-wide Orientation Support
You can easily use these methods along with your SharePoint branding strategy to roll out layout changes that inherit across the platform. In the final example in the video above, I've appended the "iPodLandscape" class to an entire zone in the SharePoint layout. This allows users to add web parts, content and other media in a zone which will automatically hide itself when the iPad is in portrait mode.

Going Further
The orientation based style sheets are great for the detection of portrait vs. landscape but we also have the ability to take it a step further. The value of window.orientation will give you a numerical value of the orientation (0, -90, 90, or 180). This allows you to also detect if the device is upside down, as opposed to simply portrait or landscape. You can listen for orientation change by appending the "onOrientationChange" attribute to the <body> tag, or by using jQuery.

2007 jQuery Form Enhancements; Preserving Site Definition

During a recent discussion with @TashasEv, the topic came up of preserving site definition when rolling out jQuery form enhancements. The preservation of site definitions is something that many people ignore, but in the interest of best practices I figured it was worth a blog post to explain some of the pros and cons, and offer some solutions.

Many of us (myself included) use jQuery to enhance out of the box (OOTB) SharePoint forms. jQuery allows us to make a variety of minor or major changes to the usability and functionality of forms either through straight jQuery or with Marc Anderson's (@sympmarc) popular jQuery Library for SharePoint Web Services (SPServices). Cascading dropdowns, content relevant fields, dynamic changes to styling, and other enhancements can greatly improve the user experience and the data entry side of a form, but can be the achilles heel of your support model if done incorrectly.

In layman's terms, the site definition is the foundation on which all OOTB SharePoint sites, templates, forms and pages are based. Essentially, this allows you to manage assets within the site by managing the site definition. An easy to understand example would be upgrading your SharePoint environment. If a version upgrade or service pack comes along that touches the site definition, all objects using that site definition will be updated accordingly. The issue lies in the fact that when you open your OOTB SharePoint form in SharePoint Designer and edit it, you essentially disconnect that form from the site definition. Six months down the road when you upgrade your environment or push that next service pack, your form will now not be updated. Additionally, if you find yourself in a position where you need to consult Microsoft for support, Microsoft can very well point out that you've customized the OOTB code, and refuse your support request.

SharePoint Designer makes it very easy to tell when you have an item that's separated from the site definition, as those files are marked with a blue information icon as seen below: So you may be asking yourself... how can I add jQuery to my forms if I can't edit the code directly. Well, there's a couple different approaches:

  1. Create a new form: One method is to create a new copy of the form. You're still going to disconnect the form from the site definition, but you'll at least preserve the default OOTB form's association with the site definition. To do this, load the OOTB form (ie: NewForm.aspx) and highlight and copy all of the code. Next, create a new .aspx page within the same folder and paste all of the code in. You can now make the necessary changes to this custom file, and save it as a different filename (ie: NewFormCustom.aspx). You can then right-click on your list in SharePoint Designer, select "Properties", and go to the "Supporting Files" tab. Here you can select your custom form using the browse button, which tells SharePoint to use your custom form rather than the default OOTB form from here on out. This method allows you to preserve the OOTB form without compromising it, and still allows you to directly edit the source of your custom form.
  2. Use a Content Editor Web Part (CEWP): Generally speaking I prefer to add my jQuery and Javascript at the code level (also Marc's recommendation on the SPServices documentation), but in the case of a form you do have the ability to add a CEWP to a form. To add a CEWP to a form, navigate to the form in your web browser (ie: NewForm.aspx) and append ?PageView=Shared&ToolPaneView=2 to the end of the URL (ie: NewForm.aspx?PageView=Shared&ToolPaneView=2). This will display the page in edit view (the equivalent of clicking "Edit Page" in the Site Actions menu of a web part page), allowing you to insert a CEWP to contain your code. This is also a useful technique should you have multiple forms requiring the same customization. In an enterprise environment, you may have multiple forms on multiple sites or lists that the same jQuery enhancement code applies to. You can save that code in a file, and point all of your CEWPs to that file, allowing centralized management of that script. The additional plus to this method, is that if you're on a Tier1 system or other access restrictions prevent you from using SharePoint Designer, this method will still work as all of your changes are done within the browser.