Vantage Point: Bob German's Weblog

Notes from BlueMetal Architects, where Bob is SharePoint Principal Architect. Here you will find postings on all things SharePoint, especially developer related topics.

Future Proof Solutions Part 1 - Developing SharePoint 2010 Solutions that become SharePoint Hosted Apps

Future Proof Solutions Part 1 - Developing SharePoint 2010 Solutions that become SharePoint Hosted Apps

Rate This
  • Comments 2

This article has a accompanying code samples posted at on MSDN Code Gallery. The samples demonstrate how to develop code that works in SharePoint 2010 (here) and also as a SharePoint 2013 App (here). The goal is to show you how to develop SharePoint web parts that can be packaged as traditional solutions or as apps. Whether you’re ready for the new App model or not, it’s not too soon to start developing in a new way that works on premises or online, today or tomorrow.

This sample focuses on a SharePoint-hosted app that runs in the web browser. It can also be packaged to run in SharePoint 2010 or 2013 in a Content Editor Web Part. The second article in the series will focus on a solution that can run as a Provider Hosted App or as Visual Web Part in SharePoint 2010 or 2013. In both cases, nearly 90% of the code is common between the SharePoint 2010 solution and the SharePoint 2013 app. The accompanying slide presentation is available here.

Take a Walk on the Client Side

Microsoft has sent a clear message to SharePoint developers everywhere: “we love you, but get your code off the SharePoint servers!” (See the article The Maturing of SharePoint Development - A Perspective on the New App Model for an architectural overview.) The new guidance is to put your code in the web browser, in Azure, on an external web site – anywhere except on the SharePoint servers themselves. This is a big departure from previous approaches, which have focused on server side code that runs directly within the SharePoint farm.

IT departments have been saying this for a long time. Julie Turner, a colleague of mine at BlueMetal Architects, recently confronted just this situation. She was asked to write a solution using custom code, but IT had turned off “sandboxed solutions”, and had a lengthy and complex process for approving code to run as a regular “farm” solution. Undaunted, she wrote the whole thing in a content editor web part. The solution was written entirely in JavaScript, and showed several charts and graphs of SharePoint content using the SharePoint client object model and a commercially available charting library for JavaScript.

Although I can’t post her code, I have posted a code sample that’s similar in design, and which will show you how to build rich SharePoint web parts entirely on the client side. It’s a compact but effective site provisioning solution that allows users to create new sites by filling in a form. Unlike the built-in SharePoint site creation page, this solution creates sites from a specific web template so they’ll all be consistent. The solution code is packaged two ways – once as a SharePoint 2010 solution that runs in a content editor web part, and once as a SharePoint 2013 “SharePoint Hosted App”.

Users view a list of sites in a web part, as shown above. If they click the “Create Site” button, they are taken to a form for creating a site. Once they fill in the site name, description, and URL, the web part creates the child site and adds it to the list.

The basis for the web part is simple HTML. The site list is shown in a <div> tag, as is the form. This HTML markup is shown in Listing 1 below.

    <!-- Main div to show the current site listing -->
    <div id="SsMainDiv">
        <div id="SsSiteListDiv">(loading site listing)</div>
        <br />
        <i><a href="#" onclick="DisplayForm();">Create a site</a></i>
    </div>
 
    <!-- Site creation form, normally hidden -->
    <div id="SsSiteFormDiv" style='display:none'>
        <table>
            <tr><td colspan="2"><h1>Create a Site</h1><br /></td></tr>
            <tr><td>Site Name:</td><td><input type="text"
                    id="SsSiteFormNameInput" /></td></tr>
            <tr><td>Description:</td><td><input type="text"
                    id="SsSiteFormDescriptionInput" /></td></tr>
            <tr><td>Url:</td><td><input type="text"
                    id="SsSiteFormUrlInput" /></td></tr>
            <tr><td>&nbsp;</td><td><input type="button"
                    value="Create site" onclick="CreateSite();" />
                </td></tr>
        </table>
        <br />
        <i><a href="#" onclick="DisplayChildSites();">Return to site
              list</a></i>
    </div>

Listing 1 – HTML for the Site Creation Form

This markup lives within the SimpleSitesWP/SimpleSitesWP.dwp file in the SharePoint 2010 solution. A .dwp file is a web part description file (sometimes these have a .webpart extension.) To make it, I simply dropped a Content Editor web part on a SharePoint page and added some “hello world” text, then exported it. I removed the “hello world” from the exported file, replaced it with my markup, and added it to a SharePoint solution in Visual Studio. The Visual Studio solution deploys the file in the Web Part gallery so a user can simply drop it on any page where they want a bit of site provisioning. There is no server code, no dll to install – nothing.

The same markup lives within a web page called Pages/SimpleSitesWP.aspx in the SharePoint 2013 app. This is a “SharePoint Hosted” app, which means that it runs in special SharePoint site called an App Web. (For more information on App Webs and SharePoint Hosted Apps, I recommend Microsoft SharePoint 2013 App Development by Ted Pattison and Scot Hillier.) A user can place it on a page using the Client Web Part, which is packaged in the SimpleSitesWP folder. The Client Web Part simply renders an IFrame, and the SimpleSitesWP.aspx page shows inside the IFrame.

So far so good – the HTML is on the page. Next, we need to add some Javascript to make the app come to life. Most of the Javascript in both the SharePoint 2010 solution and the App is in a file called app.js – more on that later. But while we’re still in the web part itself, we need to pull App.js into the browser as well as a couple of other scripts such as JQuery and a special script called AppContext.js. These scripts are loaded dynamically using SharePoint’s “Script on Demand” library (a hold-over from ASP.NET Ajax, fondly called the “Sod”.) Listing 2 shows the Javascript to load the scripts and start the application when they are finished loading.

<script language="javascript">
  // Ensure web part script and jQuery are loaded
  // Create an object to ensure we only load the scripts once, even
  // if the web part is used more than once on a page
    if (window.simpleSites == undefined) {
        simpleSites = new Object();

        // Register jQuery and the web part scripts
        RegisterSod('jquery.min.js', 
                    '../Scripts/jquery-1.7.1.min.js');
        LoadSodByKey('jquery.min.js');
        RegisterSod('appContext.js', '../Scripts/AppContext.js');
        LoadSodByKey('AppContext.js');
        RegisterSod('app.js', '../Scripts/App.js');
        LoadSodByKey('app.js');
    }
 
    // Wait until all scripts are loaded, then run the web part script
    ExecuteOrDelayUntilScriptLoaded(function () {
        ExecuteOrDelayUntilScriptLoaded(function () {
            ExecuteOrDelayUntilScriptLoaded(function () {
                ExecuteOrDelayUntilScriptLoaded(function () {
                    DisplayChildSites();
                }, 'app.js');
            }, 'appContext.js');
        }, 'sp.js');
    }, 'jquery.min.js');
  </script>

Listing 2 – Using the Scripting On Demand library to load the web part Javascript

This task complete, the DisplayChildSites() function in app.js runs. Keep in mind that the app.js files in SharePoint 2010 and in the SharePoint 2013 app are identical. This works because the code uses the SharePoint Client Object Model (Client OM), which was introduced in SharePoint 2010. Although SharePoint 2013 added a bunch of new functionality to the Client OM, this code limits itself to using the features that are found in both 2010 and 2013. Listing 3 shows the DisplayChildSites() function.

// DisplayChildSites() - Fetches the current list of sites and displays them in the
// site list div
function DisplayChildSites() {
 
    // Show only the main div
    $("#SsMainDiv").show();
    $("#SsSiteFormDiv").hide();
 
    var hCtx = GetHostContext();
    var aCtx = GetAppContext();
 
    // Get the webs using the Client OM
    this.web = hCtx.get_web();
    this.childWebs = this.web.get_webs();
 
    aCtx.load(this.web);
    aCtx.load(this.childWebs, 'Include(Title, Description, ServerRelativeUrl, WebTemplate)');
 
    aCtx.executeQueryAsync(Function.createDelegate(this, this.displayChildSitesSucceeded),
                           Function.createDelegate(this, this.displayChildSitesFailed));
}

Listing 3 – DisplaySites() function to request a list of child sites for display

There are a few interesting things in here. First is the use of jQuery, which has become pervasive for JavaScript developers everywhere. It’s so popular, in fact, that the Visual Studio template for the SharePoint 2013 app came with jQuery preloaded! I had to add it myself to the SharePoint 2010 solution. The first couple lines ensure that the main div is showing and the form div is hidden, so users can see the site listing and not the new site form when the function is called.

Following this, the code obtains the SharePoint client context. Long-time SharePoint developers will be familiar with the idea of SharePoint context – it’s a handy object that “knows” about your current SharePoint site and other runtime details so your code can act on them. The same is true on the client side, except we call it the client context. Now in this case we’re going to be a little tricky and get two contexts instead of just one. In the SharePoint 2010 case, these are the same object, but in the SharePoint 2013 App one refers to the App Web (where the app runs) and the Host Web (where we’ll be listing and creating child sites). This little piece of magic is handled by the appContext.js file, which is different in the two versions. The SharePoint 2010 solution simply loads the client context twice, while the SharePoint 2013 solution loads the App context and then uses the ProxyWebExecutor to proxy requests to the host web. This object takes Client OM calls running in the App web and – subject to the app’s permissions – runs them on the host web. Normally, cross-domain security in the browser would prohibit this. Listings 4 and 5 show the two versions of appContext.js.

Once the client contexts are available, DisplaySites() requests a list of child sites. The Client OM is batch oriented – you can request many things, and then get them all at once using the ExecuteQueryAsync() call. You can think of this like writing a shopping list – one trip to the store to get many things, rather than a separate trip for each item is much more efficient.

var spContext;          // Context for all purposes

function GetAppContext() {
    if (this.spContext == null) {
        initializeContext();
    }
    return this.spContext;
}
 
function GetHostContext() {
    if (this.spContext == null) {
        initializeContext();
    }
    return this.spContext;
}
 
// InitializeContext() - Initializes SharePoint context objects function initializeContext() {
    if (this.spContext == null) {
        this.spContext = new SP.ClientContext.get_current();
    }
}

Listing 4 – appContext.js for the SharePoint 2010 solution simply obtains the client context twice

function GetAppContext() {
    if (this.appContext == null) {
        initializeContext();
    }
    return this.appContext;
}
 
function GetHostContext() {
    if (this.hostContext == null) {
        initializeContext();
    }
    return this.hostContext;
}
 
// InitializeContext() - Initializes SharePoint context objects
function initializeContext() {
    if (this.appContext == null && this.hostContext == null) {
        var appWebUrl1 =
             decodeURIComponent(
                 getQueryStringParameter("SPAppWebUrl"));
        var hostWebUrl1 =
            decodeURIComponent(
                getQueryStringParameter("SPHostUrl"));
        var factory;
 
        this.appContext = new SP.ClientContext(appWebUrl1);
        factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl1);
        this.appContext.set_webRequestExecutorFactory(factory);
        this.hostContext = new SP.AppContextSite(appContext,
            hostWebUrl1);
    }
}
 
// GetQueryStringParameter() - Retrieves a query string parameter from
// this page's URL. SharePoint Apps pass the app and host web URL’s to
// the app page as query string arguments. Amazingly, jQuery does not
// have a function to read a query string parameter!
function getQueryStringParameter(paramToRetrieve) {
    var params =
        document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] === paramToRetrieve)
            return singleParam[1];
    }
}

Listing 5 – appContext.js for the SharePoint 2013 App uses the ProxyWebExecutor to obtain a separate context for the Host Web

Assuming the request for the list of child sites is successful, the Client OM will call DisplayChildSitesSucceeded(). By this time, the ClientOM has filled in the childWebs variable with a list of child webs. The code can iterate through them and build a table showing the sites in the site listing div.

// DisplayChildSites() - Success
function displayChildSitesSucceeded(sender, args) {
 
    // Build a simple table to show the site list
    var result = '<table border="0" class="ssTable">' +
                 '<tr><th>Name</th><th>Description</th></tr>';
 
    // Loop through the sites, adding a table row for each one
    var i = 0;
    var webEnumerator = childWebs.getEnumerator();
 
    while (webEnumerator.moveNext()) {
        var c = webEnumerator.get_current();
        if (c.get_webTemplate() != "APP") {
            var cssClassSuffix = "Odd";
            if ((++i % 2) == 0) { cssClassSuffix = "Even" }
            result += '<tr class="ssRow' + cssClassSuffix + '">' +
                         '<td class="ssCell' + cssClassSuffix + '">' +
                           '<a href="' + c.get_serverRelativeUrl() +
                               '" target="_top">' +
                               c.get_title() + '</a></td>' +
                         '<td class="ssCell' + cssClassSuffix + '">' +
                               c.get_description() +
                       '</td></tr>';
        }
    }
 
    // Complete the table and display it
    result += '</table>';
    $("#SsSiteListDiv").html(result);
}

Listing 6 – DisplaySitesSuccess() renders the child sites in the web part

There are a couple things to notice here. The first is that the childWebs collection has an enumerator property that we can read using getEnumerator(). This is used to loop through the collection. Another thing to note is that the code checks the web template for each site, and skips over any web sites created with the “APP” web template. Remember that SharePoint hosted apps run in a child site – without this logic, we’d see the app itself listed along with the other child sites in the SharePoint 2013 app version! The code loops through the child sites and builds an HTML table with a hyperlink to each one, then injects the HTML into the site listing div for display.

So far so good - already this is a useful little solution. But what if someone clicks the “Create a Site” link? A quick peek back at Listing 1 shows that this will run another Javascript fundtion, called DisplayForm(). This code, shown in Listing 7, simply clears out the form fields (in case the user already created another site), hides the site listing div, and reveals the form.

// DisplayForm() - Hide the main display and show the site creation
// form
function DisplayForm() {
    $('#SsSiteFormNameInput').val('');
    $('#SsSiteFormDescriptionInput').val('');
    $('#SsSiteFormUrlInput').val('');
    $('#SsMainDiv').hide();
    $('#SsSiteFormDiv').show();
}

Listing 7 – DisplayForm() hides the site listing and displays the site creation form instead

Now the user fills in the form and clicks the “Create Site” button. This calls yet another Javascript function, CreateSite(). The idea here is to create a site using a custom web template; in this case the web template is hard-coded to be called “SS Project Site” or, simply, “Template”. A web template is what you get if you click the “Save Site As Template” link in SharePoint site settings. You can also create one in Visual Studio, but that’s a topic for another article. The point is that people generally don’t want to create one of the out-of-the-box sites, they usually want their own content, web parts, etc. to be pre-loaded into each site to help manage a project or some other business function.

The use of a web template makes site creation a two step process. First, CreateSite() will request a list of web templates using the Client OM. If it succeeds, CreateSite2() will find the web template in question, and use its name (which contains a GUID and is not visible in the SharePoint UI) to create the child site. Listing 8 shows CreateSite() and CreateSite2().

// CreateSite() - The user clicked the "Create Site" button, so create it now
// 1st stage retrieves the host web and list of available web templates
function CreateSite() {
    $("#SsReturnLink").hide();
    $("#SsCreatingSiteMessage").show();
 
    var hCtx = GetHostContext();
    var aCtx = GetAppContext();
 
    this.web = hCtx.get_web();
    this.webTemplates = this.web.getAvailableWebTemplates(1033, true);
 
    aCtx.load(this.web);
    aCtx.load(this.webTemplates);
 
    aCtx.executeQueryAsync(Function.createDelegate(this, this.CreateSite2),
                          Function.createDelegate(this, this.createSiteFailed));
 
}
 
// CreateSite2() - The user clicked the "Create Site" button, so create it now
// 2nd stage gets the web template name and creates the site using the web template
function CreateSite2(sender, args) {
    var hCtx = GetHostContext();
    var aCtx = GetAppContext();
 
    // Find the web template and get its full name (format is
    // "{guid}#Title")
    var webTemplateName;
    var webTemplateEnumerator = this.webTemplates.getEnumerator();
    while (webTemplateEnumerator.moveNext()) {
        var t = webTemplateEnumerator.get_current();
        if (t.get_title() == 'SS Project Site' || 
            t.get_title() == 'Template') {
            webTemplate = t.get_name();
        }
    }
 
    // Get the current web
    this.web = hCtx.get_web();
 
    // Build out the WebCreationInformation for the new site
    var wci = new SP.WebCreationInformation();
    wci.set_title($('#SsSiteFormNameInput').val());
    wci.set_description($('#SsSiteFormDescriptionInput').val());
    wci.set_url($('#SsSiteFormUrlInput').val());
    wci.set_language(1033);
    wci.set_useSamePermissionsAsParentSite(true);
    wci.set_webTemplate(webTemplate);
 
    // Create the new web in the client context
    this.childWeb = this.web.get_webs().add(wci);
    aCtx.load(this.web);
    aCtx.load(this.childWeb);//, 'ServerRelativeUrl', 'Created');
 
    // Now create it on the server
    aCtx.executeQueryAsync(Function.createDelegate(this,
        this.createSiteSucceeded),
                           Function.createDelegate(this,
        this.createSiteFailed));
}

function createSiteSucceeded(sender, args) {
    alert('Your site, ' + this.childWeb.get_title() +
        ' was successfully created.');
    $("#SsReturnLink").show();
    $("#SsCreatingSiteMessage").hide();
    DisplayChildSites();
}

Listing 8 – Creating a child site from a custom Web Template

Notice that, as with the DisplayChildSites() function, the code strategically uses the app web context for some things (like telling the Client OM to load and execute), and the host web context for others (to reference child sites and web templates in the host web). In the case of SharePoint 2010, these are the same, so the code works in either case.

While the solutions are nearly identical, there is an important advantage to the SharePoint 2013 App, and that is that it runs using app permissions. That means that the user doesn’t need to have permission to create a child site in order to run it! Instead, the code runs with permissions that are granted to the App when it’s installed (the installer needs to have at least the same permissions to install it.) In the SharePoint 2010 version, the code runs under the logged-in user’s credentials, so he or she must have rights to create a child site.

This simple example is a starting point for much more ambitious projects, such as my colleague’s dashboard that runs in a Content Editor Web Part. As Javascript and its many libraries become more powerful, so can your client-side solutions. And the more complex the project, the more common code can be leveraged between SharePoint 2010 and SharePoint 2013 apps; in her case, there was more application logic, but only the packaging and AppContext.js files differed, lowering the percentage of code that will need to be rewritten when her client is ready to embrace the App model.

One final thought here is to give a shout-out to Marc Anderson, who created a powerful client-side API called SPServices. This is an option that works in earlier versions of SharePoint, such as 2007, since it calls SharePoint’s long-standing SOAP web services. When most of us were still thinking server side, Marc was already teaching us to tread lightly on the server, and to run our code on the client.

Next week I’ll post Part 2 of the series, which shows how to build future-proof apps that run on the server. This allows the code to do things that just can’t be done on the client, such as running an event handler that runs in the background when SharePoint content is changed. In the SharePoint 2010 case, the code runs in the usual place: on the SharePoint server. However through use of the Client OM, the same code runs outside of SharePoint in a Provider or Auto-hosted App in SharePoint 2013.

Thanks for reading!

 

Blog - Comment List MSDN TechNet
  • Loading...
Leave a Comment
  • Please add 4 and 2 and type the answer here:
  • Post