Tagged: office365dev

Custom site header and footer using a SharePoint-hosted add-in

In this post, I will show you how to create a custom site header and footer for SharePoint Online/Office 365 that will render on all pages using a SharePoint-hosted add-in. This could be used to post critical alerts or to specify the level of business impact of the content stored within a particular site (e.g., high, medium, or low).

This add-in will accomplish the following, all without a single line of server-side code:

  • Upon installation, deploy 3 JavaScript files to the Style Library of the host web
  • Register 3 user custom actions in the host web to ensure these JavaScript files are loaded on every page
  • Via an add-in part, provide a mechanism for users to enable/disable and set properties of the custom header and footer (e.g., message text, background/text color)
  • Store and retrieve configuration parameters for the site header and footer in the host web’s property bag

Rendering a custom header and footer

We might be tempted to edit the master page directly to add a header and a footer, but this is not considered a best practice (especially in SharePoint Online, where master page changes can be rolled out quite frequently). Instead, our add-in will make use of JavaScript techniques to insert

elements at the top and bottom of the page for our header and footer:

  • In SharePoint Online/Office 365, our header
    (with ID customHeader) will be inserted above the
    with ID suiteBarTop (for SharePoint 2013 on-premises, you’ll need to update the script to reference ID suiteBar instead).
  • Our footer
    (with ID customFooter) will be inserted below the
    with ID s4-bodyContainer.
  • NOTE: The
    IDs in SharePoint Online are never set in stone and could change at any time. If they do, you will need to update HeaderFooter.js to reference the new
    IDs.

jQuery makes it easy to create our custom header and footer

elements and insert them in the appropriate location within the DOM with its .insertBefore() and .insertAfter() functions following this approach:

$("
HEADER TEXT
").insertBefore("#suiteBarTop"); $("
FOOTER TEXT
").insertAfter("#s4-bodyContainer");

Making the footer “sticky”

Huge shout-out to Randy Drisgill for his SharePoint 2013 Sticky Footer implementation. The “sticky” footer anchors the footer

to the bottom of the page (or the bottom of the browser window if the available screen real estate exceeds the amount of content on a given page). I only needed to make one change to Randy’s StickyFooter.js implementation, and that was to account for the height offset imposed by the addition of our customHeader
:

var difference = windowheight - (bodyheight + ribbonheight + footerheight + $("#customHeader").outerHeight());

Provisioning files to the host web via JavaScript

For anyone following the OfficeDev PnP, the concept of remote provisioning to allow add-ins to deploy files to the host web should be a familiar one. However, the PnP examples make use of the .NET Managed CSOM to do this, which is a perfectly valid technique but would require us to develop a provider-hosted add-in (allowing that code to run in Azure or some other web server). Since I wanted to create a SharePoint-hosted add-in, I had to find a way to accomplish this using only JavaScript. Thankfully, I found Chris O’Brien’s post with code showing how to provision files to the host web using JavaScript. You will see that my code is based heavily on the example he provides and provisions the following files from the add-in web to the host web:

  • jQuery-1.9.1.min.js – jQuery
  • HeaderFooter.js – our logic to read the header/footer configuration data from the host web property bag and render the header
    and footer
    elements
  • StickyFooter.js – Randy Drisgill’s Sticky Footer implementation for SharePoint 2013 (with the one tweak described above)

Add-in part for setting configuration values

The add-in also includes an add-in part (deployed to and rendered from the add-in web) that allows users to enable/disable the header/footer and set the text and colors:

addinpartWhen the add-in part loads, it uses JavaScript to query the property bag of the host web to see if these settings already exist and if so, prepopulates the values in the form. Once the user has made the necessary changes, clicking Set Values will save the changes back to the property bag (again, using JavaScript).

NOTE: The need for our add-in to read and write data to and from the host web property bag requires us to request the FullControl permission at the Web scope in our add-in manifest (AppManifest.xml):


  

What about MDS?

SharePoint 2013 introduced the Minimal Download Strategy (MDS), which reduces page load time by sending only the differences when users navigate to a new page. While this is a wonderful benefit, it wreaks havoc with solutions that need to manipulate the DOM every time a new page is loaded. In other words, our header and footer may render perfectly on the home page when we first load it, but thanks to MDS when we navigate to the “Documents” library, only part of the page will actually be re-rendered. Our header and footer will not display properly (if at all) when only part of the page is re-rendered to support a new page request.

For much deeper reading on this subject, I encourage you to go through Wictor Wilen’s blog posts introducing the MDS and explaining the correct way to execute JavaScript functions on MDS-enabled sites. My code is based on Wictor’s solution and works properly in scenarios where MDS is enabled and where it is not (e.g., on Publishing sites or any site where the MDS feature has been deactivated).

We handle MDS by calling RegisterModuleInit() with the function we need to execute on every page load in HeaderFooter.js:

if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    // MDS enabled
    RegisterModuleInit(_spPageContextInfo.siteServerRelativeUrl + 'Style Library/Headerfooter.js', DJ.HeaderFooter.Render.initializeHeaderFooter);
}
// Run now on this page (and non-MDS scenarios)
DJ.HeaderFooter.Render.initializeHeaderFooter();

The code

I have posted the code for this add-in to GitHub at the following location:

https://github.com/dannyjessee/SiteHeaderFooter

I encourage you to download it, try it out in your environment, and let me know if you run into any issues with it. My sincere thanks go out to Chris O’Brien, Randy Drisgill, and Wictor Wilen for giving me the building blocks needed to put this add-in together.

Accessing SharePoint Data from Provider-Hosted Apps: Use the Right Context

I have spent the better part of the last two years working with the new app development model for SharePoint 2013. Recently I have focused on building apps that access, manipulate, and interact with data stored in SharePoint Online with Office 365. If you have done any development using the client-side object model (CSOM) for SharePoint, you understand the importance of instantiating the proper ClientContext object to access data in a particular SharePoint site. The ClientContext constructor takes as an argument the URL of a SharePoint site and allows you to access data stored in the Lists collection of the Web associated with it. In this post, I will discuss the various context objects you should use in your provider-hosted app depending on where the data your app needs to access resides and if the user’s permissions need to be considered. If you have been developing apps for SharePoint for awhile now (and even if you haven’t), I strongly encourage you to use Visual Studio 2013 and the Office Developer Tools for Visual Studio 2013 – March 2014 Update.

Host webs and app webs

When dealing with apps for SharePoint, you will become familiar with host webs and app webs:

  • Host web – the SharePoint site to which an app is installed
  • App web – the special isolated site (a unique app web is provisioned for each installation of the app) where the app for SharePoint’s internal components and content, such as lists, content types, workflows, and pages, are deployed

Note that a provider-hosted app is not required to have an app web, and in fact may not need one depending on your business requirements.

Your app will always have Full Control permissions to its app web. However, your app will need to request (and be granted) permissions by the user installing your app in order to access data in the host web. This is handled through the app manifest.

If your app needs to access data in the SharePoint site where it is being installed, you will be working with a host web context of some sort. As you will see, there are actually two different host web context objects, depending on the app authorization policy you choose.

Life made easy, thanks to SharePointContext.cs

When you create a new provider-hosted app in Visual Studio 2013, you have the option to create a new ASP.NET Web Forms or MVC application to serve as your app’s remote web application. If you are using the Office Developer Tools for Visual Studio 2013 – March 2014 Update, you also have the option to convert an existing ASP.NET web application to an app for SharePoint project (really cool!) In either case, you will notice that SharePointContext.cs is added to the remote web application project. This file contains class definitions for SharePointAcsContext and SharePointHighTrustContext, which allow you to create host web and app web context objects based on whether your trust broker is ACS (which it is with Office 365) or if you are on premises. I won’t delve into too much more detail in this post, but Kirk Evans has an outstanding writeup about these classes.

Accessing data in the app web

To access data in the SharePoint app web from your app, use the following pattern:

CSOM (C#)

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);

using (var clientContext = spContext.CreateUserClientContextForSPAppWeb())
{
    Web web = clientContext.Web;
    clientContext.Load(web);
    clientContext.ExecuteQuery();

    ListCollection lists = web.Lists;
    clientContext.Load(lists);
    clientContext.ExecuteQuery();
}

REST (C#)

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(spContext.SPAppWebUrl + "/_api/web/lists");
listRequest.Method = "GET";
listRequest.Accept = "application/atom+xml";
listRequest.ContentType = "application/atom+xml;type=entry";
listRequest.Headers.Add("Authorization", "Bearer " + spContext.UserAccessTokenForSPAppWeb);

JSOM

var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
var clientContext = new SP.ClientContext(appweburl);
var appWeb = clientContext.get_web();
var appWebListColl = appWeb.get_lists();
clientContext.load(appWebListColl);
clientContext.executeQueryAsync(onAppWebGetListSuccess, onError);

Accessing data in the host web

To access data in the SharePoint host web (the SharePoint site where your app is installed) from your app, use the following pattern:

CSOM (C#)

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);

using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
    Web web = clientContext.Web;
    clientContext.Load(web);
    clientContext.ExecuteQuery();

    ListCollection lists = web.Lists;
    clientContext.Load(lists);
    clientContext.ExecuteQuery();
}

REST (C#)

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(spContext.SPAppWebUrl + "/_api/web/lists");
listRequest.Method = "GET";
listRequest.Accept = "application/atom+xml";
listRequest.ContentType = "application/atom+xml;type=entry";
listRequest.Headers.Add("Authorization", "Bearer " + spContext.UserAccessTokenForSPHost);

JSOM

var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
var hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var clientContext = new SP.ClientContext(appweburl);
var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);
clientContext.set_webRequestExecutorFactory(factory);
var appContextSite = new SP.AppContextSite(clientContext, hostweburl);
var hostWeb = appContextSite.get_web();
hostWebListColl = hostWeb.get_lists();
clientContext.load(hostWebListColl);
clientContext.executeQueryAsync(onHostWebGetListSuccess, onJSOMError);

Note that using JSOM, we still need to construct a ClientContext for the app web before we generate an AppContextSite for the host web, made possible through the SP.ProxyWebRequestExecutorFactory.

A note about the app-only authorization policy

By default, authorization checks in the host web succeed only if both the current user and the app have sufficient permissions to perform the action in question, such as reading from or writing to a list. We are reminded that the user’s permissions are taken into account based on the names of the context and access token objects we use in these scenarios: for instance, CreateUserClientContextForSPHost and UserAccessTokenForSPHost. However, your app has the ability to do something akin to running with elevated privileges using the app-only policy for authorization. Also controlled through the app manifest, the app-only policy is useful when an app doesn’t need or want to consider the permissions of the current user. In Visual Studio 2013, you can specify that your app would like to have the ability to use the app-only policy by checking this box in the AppManifest.xml editor, on the Permissions tab:

apponly

That being said, just because your app is granted this permission does not mean that you can use the same host web context or access token as before to automatically leverage it. To access data from the SharePoint host web (taking only your app’s permissions into account and ignoring the current user’s permissions) from your app, use the following pattern:

CSOM (C#)

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);

using (var clientContext = spContext.CreateAppOnlyClientContextForSPHost())
{
    Web web = clientContext.Web;
    clientContext.Load(web);
    clientContext.ExecuteQuery();

    ListCollection lists = web.Lists;
    clientContext.Load(lists);
    clientContext.ExecuteQuery();
}

REST (C#)

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
HttpWebRequest listRequest = (HttpWebRequest)HttpWebRequest.Create(spContext.SPAppWebUrl + "/_api/web/lists");
listRequest.Method = "GET";
listRequest.Accept = "application/atom+xml";
listRequest.ContentType = "application/atom+xml;type=entry";
listRequest.Headers.Add("Authorization", "Bearer " + spContext.AppOnlyAccessTokenForSPHost);

Remember that in order to use the app-only policy, your app must request and be granted this permission by the site owner who installs your app. Also note that there is no JSOM example using the app-only policy because apps that do not make OAuth authenticated calls (such as apps that are only JavaScript running in the app web) cannot use the app-only policy.

As you can see, the code you write in each of the above scenarios (accessing data in the app web, host web, or using the app-only authorization policy) is identical except for the method or property you use from the SharePointContext class to get the appropriate context or access token. Understanding these subtle differences is vitally important when making sure your app has the ability to access and manipulate the SharePoint data it needs.