SharePoint App Part Resizing, When Using AngularJS
Provider Hosted Apps
Sometimes it’s necessary to embed rich, non-SharePoint functionality in a SharePoint site. The preferred way to do this in SharePoint 2013 is to use a Provider Hosted App. SharePoint then acts as a “host” for the app, which can function independently. This gives the ability to layer in complex business rules, perform development cycles AWAY from SharePoint, and then embed the functionality in the site.
IFrame Presentation
Taking this route, one challenge is to ensure that the app appears as part of the page. Provider Hosted Apps are hosted in an iframe, but users need not be aware of this fact. If not done properly, parts of the app might disappear or for scroll bars may appear in the frame. Generally the iframe content cannot control the size of the “parent” window.
So it’s up to the developer to ensure that the iframe is sized properly for the app. Fortunately, Microsoft has anticipated this issue and given us some javascript “hooks” to do this. There are a number of blogs on how to achieve this, from simple implementation to complex ones . Bottom line, your hosted javascript code needs to send a message similar to this one to the parent window:
<message senderId=12345ABCD>resize(100%,800px)</message>
Additional Challenges with AngularJS
Resizing the parent iFrame is a simpler task with a “traditional” (mostly server side) app – then page size changes when the page reloads, and the necessary logic can be run then.
But with AngularJS (a single page app), the iframe needs to resize when we move from one “view” to another, or the page size changes due to the underlying javascript logic. When resizing, we need to know what the size of our content will be. Fortunately, this is not rocket science – it just requires a “harmonic convergence” of code.
Step 1: Add wrapper HTML
Identify or create a wrapper div which will contain all HTML in your app. We will check this wrapper to determine how large the iframe should be.
<body ng-app="appName"> <div main-wrapper> <!-- Add your site or application content here --> <div ng-view=""></div> <div> </body>
Step 2: Add Resizing Code
Add some javascript code to resize the iframe. The following code assumes that only the height of your app will vary. Note the chrome fix – this is another issue that applies only to Angular implementations.
"use strict"; function adjustFrameSize(contentHeight) { var senderId, resizeMessage = '<message senderId={Sender_ID}>resize({Width}, {Height}px)</message>'; var args = document.URL.split("?"); if (args.length < 2) return; var params = args[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]); senderId = senderId.split("#")[0]; //for chrome - strip out #/viewname if present } } var step = 30, finalHeight; finalHeight = (step - (contentHeight % step)) + contentHeight; resizeMessage = resizeMessage.replace("{Sender_ID}", senderId); resizeMessage = resizeMessage.replace("{Height}", finalHeight); resizeMessage = resizeMessage.replace("{Width}", "100%"); console.log(resizeMessage); window.parent.postMessage(resizeMessage, "*"); }
Step 3: Add an Angular Directive
In your AngularJS code, create a directive which will attach to your wrapper div. What you’re doing here is creating an event which can be fired at any time from your app, to trigger a resize of the iframe based on the height of your wrapper div.
.directive('mainWrapper', function ($timeout) { return { restrict: 'A', link: function (s, e, attrs) { s.$on('resizeframe', function () { $timeout(function () { //timeout ensures that it's run after the DOM renders. adjustFrameSize(e[0].offsetHeight); }, 0, false); }); } }; })
Step 4: Trigger the Resizing Event
Finally, you need to instruct the event to fire at the appropriate times. This might be when a controller is intially accessed, or perhaps when data are accessed. The strength of this method is that you can control when resizing occurs. You need to inject $scope and use $broadcast. Here are a couple of examples:
.controller('FirstPageControler', function ($scope) { //resize frame when controller loads $scope.$parent.$broadcast('resizeframe'); }) .controller('AnotherControler', function ($scope, webServices) { webServices.reports().then(function (response) { $scope.reports = response; //resize frame upon successful return from a service. //presumably we have returned with data that will resize the page. $scope.$parent.$broadcast('resizeframe'); }, function (response) { $scope.reports = response; }); })
That should do the job! Good luck, and feel free to leave a comment if you’ve found a better way.