Introducing app script part pattern for Office365 app model

Office365 or SharePoint app model has numerous different patterns to modify the end user experience. One of the typical structures is the usage of app parts, which are really well document in the MSDN. These app parts are basically iframes on steroids, since they provide for example additional capabilities for parameterize the individual instances. One of the down sides of the app parts is however that iframes do not work that well in responsive design scenarios and browsers could in general have some issues with them.

Using app parts is not however the only model on how to provide user experience customizations to the SharePoint sites. Completely valid alternatives are for example following models which are all demonstrated in the Office AMS examples. We have tremendous capabilities in the JSOM or REST APIs, which we can take advantage without any server side code as well.

  • JavaScript injection
    • Add custom action to the host web to execute JavaScript as part of the page execution. Good demonstration can be found from the Office AMS package under Samples\Core.JavaScriptInjection
  • Place script part or content editor part to the page
    • Place script to the script part or content editor web part located in the page which users are accessing. Downside is that you would have multiple instances of the script if the script is directly added to the web parts
  • Script in page layout
    • Place needed JavaScript to the page layout as embedded JavaScript. Suitable model if needed code is only needed in single page layout or you have limited page layout visibility to custom one’s which all reference same centralized file.
  • Script in custom master page
    • Place or reference JavaScript from the master page. In general we do recommend to avoid custom master pages when possible (will write more on this soon), so not really the best pattern to use, but would work.

Following chapters are introducing a pattern which is already used by multiple customers. I’ve named this as app script part pattern, but this is just my personal terminology for trying to differentiate the approach from other options.

 

App script part pattern

I’ve been lately working closely with the Yammer capabilities and one of the things Yammer provides is easy way of integrating their feeds to the web sites by simply adding few lines of html to the pages and this got me thinking on the possibilities of taking advantage of this in app model customizations. What if we would actually relocate or reference the script on the page content in similar ways as within the case of Yammer, so that we don’t execute the code in the different iframe in first place.

 

Here’s a simple logical architecture of the pattern.

 

image

 

  1. SharePoint environment in Office365 or in on-premises
  2. App Script part referencing external JavaScript file and having the div for marking the location where the information is injected
  3. Actual JavaScript file stored in the provider hosted app side
  4. Provider hosted app platform could be Microsoft Azure or any other platform where the JavaScript file can be hosted in away that it can be reached from the pages or by the browser when page html is processed

This pattern is pretty commonly used cross the industry to integrate systems between the others using JavaScript embedding capabilities. This scenario sample shows how to achieve the similar structure using typical SharePoint provider hosted pattern and how we can provide our extension to be easily available for end users using the web part gallery.

Good examples of the pattern usage is for example the mentioned Yammer embedding mechanism or how interactive maps are integrated to the sites using Bing or Google. More on those patterns from following locations.

In each of the above scenarios we reference to JavaScript and we use specific div for actually dynamically then contain the actual referenced functionality. Here’s an example of Yammer embed command with JavaScript reference and the div marker to define the location of the capability in the page.

 <script type="text/javascript" src="https://assets.yammer.com/assets/platform_embed.js"></script>
 <div id="embedded-feed" style="height:400px;width:500px;"></div> 
 <script>
     yam.connect.embedFeed(
       { container: '#embedded-feed',
         network: 'veskuonline.com'  // network permalink
     });
 </script>
  

This provides more seamless and dynamic integration option than using app part in iframe. This also means that this is suitable option for example for responsive user interface design. We could also just simply deploy redefined script web parts to the SharePoint sites which would have the reference and needed html for provider hosted app reference. This would give the end users opportunity to add additional functionalities to the sites as needed using simply normal SharePoint user experience with add a web part capability, but the actual code would be still coming from provider hosted app side.

One down side of this approach is that if you would need to provide complex parameterization for each instance on the page, this could be complex to achieve, but not impossible. You could pretty easily recognize when the page is in edit mode and then provide needed parameterization options from embedded JavaScript. Any configuration could be stored for example to the provider hosted app side. One additional thing to notice is that since we are injecting new web part option to the web part gallery, deployment of the web part definition (.webpart file) requires tenant administration permissions, so this model is not available for apps hosted in the app store. This tenant permission requirement is simply to avoid any security issues by injecting malicious JavaScript to pages.

In production we could be running these scripts easily from Windows Azure or from any other centralized location where they can be referenced from the SharePoint pages. This also gives us easy way to update the script, since it’s not stored in the actual SharePoint page, it’s rather loaded completely from the provider hosted environment.

 

End user experience

In the case of this sample, we are provisioning app script part to the web part gallery by demand from the provider hosted app, but just as well you could be doing this automatically as part of your provisioning logic or alternatively push the app script part to the site collections as disconnected remote operation.

image

Once the modification has been executed, we can move to the host web and start editing the page for adding a Web Part. Notice that we have new category called “App Script Part” and we can locate new “User Profile Information” web part under that category. I simply used custom group name and provided a custom icon for the user profile information script part to make it look polished.

image

 

After adding the app script part to the page, we are able to see some user profile information from the particular user like in following picture.

image

 

Notice that since we are actually executing the JavaScript from provider hosted app in context of the page, we have full control of the page layout. This means that output can be used with responsive sites or it scales in general based on the layout it is used. This is obviously completely up to the JavaScript which is responsible of rendering the output, but you don’t have to do anything specific for providing automatic resizing of the layout, like you would need to do with app parts.

image

If we take the page in edit mode and have a closer look on what is that app script part, we are able to see that it’s actually a predefined script web part, which has simply a reference to the JavaScript located in the provider hosted app side.

image

Liked noted already, usage of the local host is not obviously something which would work in the production. You could deploy your JavaScript file to some centralized location similarly as shown with Yammer or Bing maps. This could be just as well Azure web site where you host your provider hosted app eventually for the Office365 sites.

Wait? End user could break it! – Yes, they certainly could do that. They could modify the web part and break it and most likely someone will do that. They could get the functionality however back easily by adding web part again from the web part gallery as many time as needed. If end users have edit page capability, they can certainly modify the page and get rid of web parts or app parts in general.

 

Actual code for deploying the app script part to the host web using CSOM is pretty simple and straight forward. We have web part file definition (.webpart file) with predefined content, which is uploaded to the web part gallery using FileCreationInformation object like in any remote provisioning pattern.

 var folder = clientContext.Web.Lists.GetByTitle("Web Part Gallery").RootFolder;
 clientContext.Load(folder);
 clientContext.ExecuteQuery();
  
 //upload the web part file to web part gallery
 using (var stream = System.IO.File.OpenRead(
                 Server.MapPath("~/userprofileinformation.webpart")))
 {
     FileCreationInformation fileInfo = new FileCreationInformation();
     fileInfo.ContentStream = stream;
     fileInfo.Overwrite = true;
     fileInfo.Url = "userprofileinformation.webpart";
     File file = folder.Files.Add(fileInfo);
     clientContext.ExecuteQuery();
 }

 

After the file has been uploaded, we do a small adjustment for the group metadata so that we can easily locate the web part from the web part gallery. Code could be optimized for getting the exact item with query filter, but I was lazy. Sorry for that.

 // Let's update the group for just uploded web part
 var list = clientContext.Web.Lists.GetByTitle("Web Part Gallery");
 CamlQuery camlQuery = CamlQuery.CreateAllItemsQuery(100);
 Microsoft.SharePoint.Client.ListItemCollection items 
                                         = list.GetItems(camlQuery);
 clientContext.Load(items);
 clientContext.ExecuteQuery();
 foreach (var item in items)
 {
     // Just random group name to diffentiate it from the rest
     if (item["FileLeafRef"].ToString().ToLowerInvariant() 
                                 == "userprofileinformation.webpart")
     {
         item["Group"] = "App Script Part";
         item.Update();
         clientContext.ExecuteQuery();
     }
 }

 

Code to gain access to user profile properties

Here’s the code we use in the JavaScript file in the provider hosted app side to gain access on the user profile and to output the needed html. I’m not really a JavaScript developer, so code is not that clean, but does demonstrate the pattern. We use user profile JSOM to access the user profile store and whole html structure is dynamically created.

  
 RegisterModuleInit("userprofileinformation.js", sharePointReady); //MDS registration
 SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sharePointReady);
  
 // Create an instance of the current context.
 function sharePointReady() {
     clientContext = SP.ClientContext.get_current();
  
     var fileref = document.createElement('script');
     fileref.setAttribute("type", "text/javascript");
     fileref.setAttribute("src", "/_layouts/15/SP.UserProfiles.js");
     document.getElementsByTagName("head")[0].appendChild(fileref)
  
     SP.SOD.executeOrDelayUntilScriptLoaded(function () {
  
         //Get Instance of People Manager Class       
         var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);
  
         //Get properties of the current user
         userProfileProperties = peopleManager.getMyProperties();
         clientContext.load(userProfileProperties);
         clientContext.executeQueryAsync(Function.createDelegate(this, function (sender, args) {
             var firstname = userProfileProperties.get_userProfileProperties()['FirstName'];
             var name = userProfileProperties.get_userProfileProperties()['PreferredName'];
             var title = userProfileProperties.get_userProfileProperties()['Title']
             var aboutMe = userProfileProperties.get_userProfileProperties()['AboutMe'];
             var picture = userProfileProperties.get_userProfileProperties()['PictureURL'];
  
             var html = "<div><h2>Welcome " + firstname + "</h2></div><div><div style='float: left; margin-left:10px'><img style='float:left;margin-right:10px' src='" + picture + "' /><b>Name</b>: " + name + "<br /><b>Title</b>: " + title + "<br />" + aboutMe + "</div></div>";
  
             document.getElementById('UserProfileAboutMe').innerHTML = html;
         }), Function.createDelegate(this, function (sender, args) {
             console.log('The following error has occured while loading user profile property: ' + args.get_message());
         }));
     }, 'SP.UserProfiles.js');
 }

 

Summary

I called this approach as a app script part pattern, but as you could see from the demonstration and from the code, this was simply a predefined script web part, which had a reference to the JavaScript file hosted from the provider hosted app. Pretty simple scenario, but at the same time it shows an alternative option to control the rendering logic with still remaining control with single file, which will help on any future updates.

One valid alternative would be to place the script to the individual script part instances, but this would mean challenge for the future maintenance or updates, since you’d face to iterate all the pages in site collections to update all of the script parts. By having script part just referencing the file outside of the SharePoint, we have a single location to update any future updates or enhancement for our code.

App parts have their use scenario as well, but this blog post was for demonstrating alternative route for achieving pretty similar user experience without iframes. There are obviously advantages and disadvantages on each approach, but it’s good to have different patterns which we can take based on the actual business requirements. App parts could be something what suites for one requirement and embedded JavaScript for another.

 

References

Here’s some useful references on the covered topics.

Comments

  • Anonymous
    July 08, 2014
    Very nice! I've used this and it's really easy, no need to resize iframes etc. You can even use knockout.js or similar mv* library if they allow binding the model to a given element such as a div, not the whole page (which is the default). So then your script editor web part contains the template html, and the script's "startup" logic. Like you said, the only thing this approach lacks is the ability to easily configure the "web part". Obviously this kind of functionality could be built, but I tend to go for app parts when we need to provide the user with some configuration options. Here's an idea for the product group ;) A question, though: wouldn't it be more simple to deploy the javascript to the site's libraries if you just want a simple solution? I guess you'd need to deploy the js to all site collections, which is kind of a bummer, though.

  • Anonymous
    July 08, 2014
    Hi Jarmo, deploying JS to the host web would be indeed simpler for simple solutions, but would then mean that you'd have to have access to iterate through each site collection or site when we update, like you mentioned. Using one centralized JavaScript we can update ALL sites once... or when the client side caching will expire, but there's only one version of the file. Both of the above options have been used in production with our customers.

  • Anonymous
    July 08, 2014
    I use the same philosophy for my "SharePoint-Hosted app" about Custom actions and scriptlink to addEventListener Radar JS in the Host Web to capture postMessage from app parts and then associate actions in the SP pages, but yes like you said malicious code could be added. Greate post like allways, Kind regards

  • Anonymous
    July 08, 2014
    The comment has been removed

  • Anonymous
    July 10, 2014
    Bamboo Solutions has been offering "CloudParts" for years, take a look :-) store.bamboosolutions.com/cloudpartscategory.aspx Thanks, Jonas

  • Anonymous
    July 10, 2014
    Thanks Jonas, Looks pretty much the same concept and definitely nicely packaged stuff. This is definitely nothing new and could have been done already with Sandbox solutions or with even older technologies. It's just a way to reference JavaScirpt from one centralized location, which is great pattern. Personally I've been just worried on the fact that people have started thinking that app model is just app parts, but in reality we have plenty of other tooling options like this one, which have been used for years.

  • Anonymous
    July 10, 2014
    Hi Vesa, I agree, keep up your excellent work :-) Thanks, Jonas

  • Anonymous
    August 06, 2014
    Great work.  Is it possible to make REST based calls to the provider host web with the pattern.  I have been trying to access list data from app web but have been running into access issues.  Thanks for your time. Justin

  • Anonymous
    August 11, 2014
    Hi Justin, you might be running into cross domain issues with those calls, slightly depending on the model you are using. Here's some information related on how to tackle that, if I got the issue correctly - msdn.microsoft.com/.../fp161183(v=office.15).aspx

  • Anonymous
    June 07, 2015
    Good idea and we use similar approach in our products. But your approach requires Full Control for adding item to web part gallery. This is not possible for SharePoint App Store, full control is not allowed there. So, it is not so useful for Online. Do you have any suggestions for this?

  • Anonymous
    June 07, 2015
    The comment has been removed

  • Anonymous
    October 15, 2015
    Is it possible for an app script part to access data (e.g. items from a SP list) in the app's app web? NB: I'm not talking about the app's remote / provider-hosted web. The "SPAppWebUrl" query string variable obviously won't be available here (since the app script part resides on a page in the host web) so how would we get the URL of the app web?

  • Anonymous
    November 10, 2015
    Hi Nick, app script part model basically means that we run the code in the context of the host web, which then means that you can't access app web information using any traditional methods. In this case you'd be rather looking on storing whatever data to the host web so that you could access that more easily from the script.