4 Steps to smarter App Part resizing

Wow it feels like forever since I wrote something and finally I got some good new content. I started to research a little bit more on the resizing of App Parts in SharePoint Hosted Apps and came up with some really small changes in basic CSS and JS that make App Part resizing a lot more stable and X-browser compatible. #winning

 

1. No horizontal scrolling

In this example I’ll use an App that Lists Apps on the Host Web. This particular App displays a list of Title just below one another. Nothing fancy. However when we want to display the page in an App Part, this happens.

Capture1

Visually it seems that we have more then enough space between the Titles and the end of the IFrame (300px width by default). Still we see a nasty scrollbar! What happened? Inspecting some elements up in the DOM explains a lot. Very funny SharePoint.

Capture1-2

The defaultcss.ashx causes trouble! This control is loaded on the page by default in an ASPX page like Default.aspx or most likely by a dev in a plain HTML page to keep default SharePoint branding alive in the App. That is a good principle, but this width style is not really helping us out.

Full Width Power

In order to get rid of the scrollbars in this scenario there’s a simple CSS fix we can apply in our App.css.

#contentBox { min-width:100%; margin:0; }
#contentRow { padding:0; }

This way we make sure no weird margins or paddings will cause our App Part more pain and it’s also going to help us when resizing the WebParts height later on.

Capture2

Better! Now the content, that we know for sure should fit, actually fits. Width wise. Still the height of the App Part is not right.

 

2. Resize your App Part

Resizing App Parts using the postMessage technique is nothing new to most people. We need a function in our App that sends a message to the parent of the IFrame. SharePoint can pickup this message and then resizes the IFrame for you using a given width and height. I used the following code

resizeIFrame = function (height, width) {
 if (window.parent == null) return;
 var senderId = '';
 var params = document.URL.split("?")[1].split("&");
 for (var i = 0; i < params.length; i = i + 1) {
  var param = params[i].split("=");
  if (param[0].toLowerCase() == "senderid")
   senderId = decodeURIComponent(param[1]);
 }

 //Bug for self reference in href='#'.
 senderId = senderId.replace('#', '');
 var message = "<Message senderId=" + senderId + ">"
 + "resize(" + width + "," + height + ")</Message>";
 window.parent.postMessage(message, "*");
}

In this common piece of code I added a check for “#”. When this is used in the App (I came cross it once), the QueryString is extended with this “#”. This causes the SenderId, which is always last in the QueryString, to be compromised and the Resize function from SharePoint to fail!

Function call

To call this function we first determine the height of the DOM element that holds all the content. The width shall be “100%” to have an App Part that will always fill the entire space.

var height = jQuery('#OverView').outerHeight(true);
var width = "100%";
resizeIFrame(height, width);

This function resizes the entire iframe to the dimensions given.

Capture3

But wait! there is still a vertical scrollbar! Something must be wrong. In the Elements File of my ClientWebPart I removed the Height and Width properties of the App Part.

Capture3-1

Here is where a very weird phenomenon shows itself. Since we don’t have any requirements set regarding width or height, SharePoint sets a default width and height to the AppPart which is the same as the ClientWebPart xml states when it is just created.

Capture3-2

Here our default width is 300px. The funny bit is that because we use the ResizeIFrame method, the width is only set to 100% after the Resizing is done. To make it even more complex, the Height is meisuring the content that now is rendered within 300px width. Therefor the height also cannot be trusted. This makes the resizing of the App very buggy. Now we see it still as 300px width. Refreshing a couple of times shows 100% width but still with a vertical scrollbar and height that is just a couple of px off. Some DOM element must be holding back some padding info.

 

3. Body Container

Again the main obstacle here is some styling that comes from defaultcss.ashx. By default the CSS sets a padding-bottom:35px; on #s4-bodyContainer. This is why we add to our App.css this piece as well.

#s4-bodyContainer { padding:0; }

Capture4

Instantly this is starting to look good! The App Part now scales over the entire width of the Page using a correct height and most important: NO scrollbars! Since I placed my App Part in the text part of this SitePage it is displayed in full width. When I use a Publishing page with multicolon WebPart Zones, it will also adept to the width of the colons. This comes in very handy for instance with use of Bootstrap.

 

4. One last thing

One last nag, which is actually really tough. Thereby I don’t mean the fix, that ones easy. I’m talking about consideration on how to deploy the fix. The thing is, as I just explained, the height is calculated at the wrong time in rendering. In order to get the height right, the IFrame must have it’s width set to 100% first. We’ve seen SharePoint set it to a default value of 300px. When a dynamic list of <span>’s wrap over 3 lines for instance, the height at a 100% width is very different from the height at 300px width.

A fix here can be set all IFrames to a default width of 100%. I don’t see why you shouldn’t do that in the first place. But still it depends right.

Custom CSS

The best option in my opinion is to use the capability to upload and add your custom CSS when using Publishing Sites. You can do this in the MasterPage section of the Site Settings. Just create a blank CSS file and add

iframe{ width:100%; }

When you aren’t using publishing sites, there is another possibility.

UserCustomAction

You can add some code to your App to deploy a UserCustomAction to the HostWeb. In the UCA you can add a ScriptBlock that writes this CSS style to every page. This is a dangerous option when not done right. Waldek wrote about these things and explained why it can break your site. The following code will do so, but keep in mind that everytime this executes another UCA is created. You still have to create a “Run Once” type of mechanism.

P42.ScalingAppPart.setFrameWidth = function ($) {
 var execute = function () {
  var deferred = $.Deferred();
  var hostUrl = decodeURIComponent(P42.Utils.getQueryStringProperty("SPHostUrl"));
  var appWebUrl = decodeURIComponent(P42.Utils.getQueryStringProperty("SPAppWebUrl"))

  var context = new SP.ClientContext(appWebUrl);
  var factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);
  context.set_webRequestExecutorFactory(factory);
  var appContextSite = new SP.AppContextSite(context, hostUrl);
  var site = appContextSite.get_site().get_rootWeb();
  var userCA = site.get_userCustomActions();

  var customAction = userCA.add();
  customAction.set_location("ScriptLink");
  customAction.set_scriptBlock("document.write('<style>iframe{width:100%}</style>');");
  customAction.set_sequence(1000);
  customAction.update();

  context.executeQueryAsync(function () {
   deferred.resolve();
  }, function (sender, args) {
   deferred.reject(sender, args);
  })

  return deferred.promise();
 };

 return {
  execute: execute
 }
}(jQuery);

Famous Last Words

These steps will get you some smoother App Part resizing experience that will work cross-browser. By removing all the extra margins and paddings you are now in control over the exact width and height of your App Part. I never got it right untill I put some work in it, hopefully this post will help you too getting it right.

– Cas

4 comments

  1. Office Blogs says:

    Office 365 開発者向けポッドキャスト第 20 回: Open XML SDK について

    (この記事は 2014 年 10 月 16 日に Office Blogs に投稿された記事 Office 365 Developer Podcast: Episode 020 about Open XML

  2. Ramesh says:

    Thank you for you help. Precisely, the below line.
    //Bug for self reference in href=’#’.
    senderId = senderId.replace(‘#’, ”);

Leave a Reply

Your email address will not be published. Required fields are marked *