01 March 2013

Resizing App Parts with PostMessage in SharePoint 2013

Hi there! 

We have moved our blog to the following address: 


http://blog.ctp.com/category/microsoft-competence/



In case you have any comment, please leave it using the new URL. Soon we will stop monitoring the blog entries in this site. Also our new material will be posted only in the new URL.

Thanks!




Introduction
In SharePoint 2013, App Parts are a great way of aggregating the content from distinct Apps on a hosted page. App Parts use iFrames to surface the App’s content and because Apps are in distinct domains than the hosted web, they cannot manipulate directly the hosted page at DOM level (for details please refer to the Same Origin Policy that web browsers enforce).
Although security constraints, there are cases where the Apps should be allowed to make changes on the hosted page. For instance, when the content the Apps are displaying does not fit into their App Parts. In this case, SharePoint 2013 allows the resizing of App Parts through the HTML5 Cross-Document Messaging. Find below an example about how this functionality works.

The Code
Find here the page of a SharePoint 2013 App which can be referenced by an App Part. The App page requests the hosted page to resize the App Part as the content grows or shrinks by using the method postMessage. The hosted page in SharePoint takes care of doing the resizing itself.
To test the code, just create a SharePoint 2013 Hosted App, add to the project a Client Web Part (known as well as App Part), create an ASPX page with the code below and set the Client Web Part to reference to that page. Afterwards, deploy the App and add the Client Web Part into a page in the hosted web. Detailed steps can be found in the References section below.


<%@ Page language="C#" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<!-- SharePoint pages require this when displaying them in an App Part. -->
<!-- (Reference: http://msdn.microsoft.com/en-us/library/jj220046.aspx) -->
<WebPartPages:AllowFraming ID="AllowFraming" runat="server" />

<html>
<head>
    <title>App Part Communica</title>
    <script src="../Scripts/jquery-1.7.1.min.js"></script>

    <script type="text/javascript">
        // Set the style of the app part page to be consistent with the host web.
        // Get the URL of the host web and load the styling of it.
        function setStyleSheet() {
            var hostUrl = ""
            if (document.URL.indexOf("?") != -1) {
                var params = document.URL.split("?")[1].split("&");
                for (var i = 0; i < params.length; i++) {
                    p = decodeURIComponent(params[i]);
                    if (/^SPHostUrl=/i.test(p)) {
                        hostUrl = p.split("=")[1];
                        document.write("<link rel=\"stylesheet\" href=\"" + hostUrl +
                            "/_layouts/15/defaultcss.ashx\" />");
                        break;
                    }
                }
            }
            // if no host web URL was available, load the default styling
            if (hostUrl == "") {
                document.write("<link rel=\"stylesheet\" " +
                    "href=\"/_layouts/15/1033/styles/themable/corev15.css\" />");
            }
        }
        setStyleSheet();
    </script>
</head>

<body style="background-color: #f5f5f5">

    <!-- the content considered for the resizing -->
    <div id="content">
        <p>SenderId: <span id="senderId"></span></p>
        <input type="button" onclick="Communica.Part.addItem();" value="Add Item"/>&nbsp;
        <input type="button" onclick="Communica.Part.removeItem();" value="Remove Item"/>
        <ul id="itemsList">
            <li>Item</li>
        </ul>
    </div>

    <script lang="javascript">
        "use strict";

        // define a namespace
        window.Communica = window.Communica || {};

        $(document).ready(function () {
            // initialise
            Communica.Part.init();
        });

        Communica.Part = {
            senderId: '',      // the App Part provides a Sender Id in the URL parameters,
                               // every time the App Part is loaded, a new Id is generated.
                               // The Sender Id identifies the rendered App Part.
            previousHeight: 0, // the height
            minHeight: 0,      // the minimal allowed height
            firstResize: true, // On the first call of the resize the App Part might be
                               // already too small for the content, so force to resize.

            init: function () {
                // parse the URL parameters and get the Sender Id
                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")
                        this.senderId = decodeURIComponent(param[1]);
                }

                // find the height of the app part, uses it as the minimal allowed height
                this.previousHeight = this.minHeight = $('body').height();

                // display the Sender Id
                $('#senderId').text(this.senderId);

                // make an initial resize (good if the content is already bigger than the
                // App Part)
                this.adjustSize();
            },

            adjustSize: function () {
                // Post the request to resize the App Part, but just if has to make a resize

                var step = 30, // the recommended increment step is of 30px. Source:
                               // http://msdn.microsoft.com/en-us/library/jj220046.aspx
                    width = $('body').width(),        // the App Part width
                    height = $('body').height() + 7,  // the App Part height
                                                      // (now it's 7px more than the body)
                    newHeight,                        // the new App Part height
                    contentHeight = $('#content').height(),
                    resizeMessage =
                        '<message senderId={Sender_ID}>resize({Width}, {Height})</message>';

                // if the content height is smaller than the App Part's height,
                // shrink the app part, but just until the minimal allowed height
                if (contentHeight < height - step && contentHeight >= this.minHeight) {
                    height = contentHeight;
                }

                // if the content is bigger or smaller then the App Part
                // (or is the first resize)
                if (this.previousHeight !== height || this.firstResize === true) {
                    // perform the resizing

                    // define the new height within the given increment
                    newHeight = Math.floor(height / step) * step +
                        step * Math.ceil((height / step) - Math.floor(height / step));

                    // set the parameters
                    resizeMessage = resizeMessage.replace("{Sender_ID}", this.senderId);
                    resizeMessage = resizeMessage.replace("{Height}", newHeight);
                    resizeMessage = resizeMessage.replace("{Width}", width);
                                        // we are not changing the width here, but we could

                    // post the message
                    window.parent.postMessage(resizeMessage, "*");

                    // memorize the height
                    this.previousHeight = newHeight;

                    // further resizes are not the first ones
                    this.firstResize = false;
                }
            },

            addItem: function () {
                // add an item to the list
                $('#itemsList').append('<li>Item</li>');
                Communica.Part.adjustSize();
            },

            removeItem: function () {
                // remove an item from the list
                $('#itemsList li:last').remove();
                Communica.Part.adjustSize();
            }
        };
    </script>
</body>
</html>

Conclusion
An App page can resize its App Part by posting messages to the hosted page. The end result is a more responsive UI as follows:
The App's page (grey background) in an App Part.

After the content grew, the App Part has resized.
If the content shrinks, the code requests the App Part to be reduced.



References


6 comments:

Shawn Tucker said...

Thank you for posting this clear and accurate tip. It's been a frustrating experience trying to work around the nuances in Sharepoint 2013 app development.

A tip for others out there, make your app part completely fluid and work properly in Internet Explorer by initializing and firing Communica on the $(window).load event, and call the adjustSize method on the $(window).resize event. You'll want to remove the +7 from this line [ height = $('body').height() + 7], to prevent it from resizing forever.

Matthew said...

Hi,

This is exactly what I was looking for. Only problem I've got now is that I'm developing for Office 365 and can't seem to find a copy of Microsoft.Sharepoint.dll. Any ideas where I can find this?

Regards,
Matthew

Leandro Bernsmüller said...

Microsoft.Sharepoint.dll is for server side development, you would need a SP development machine for that.
I assume you are looking for the Microsoft.SharePoint.Client.dll which you can find looking in Google for "redistributable Microsoft.SharePoint.Client.dll sharepoint 2013".

dunja filc said...

Free Microsoft 4200 points Goods
undoubtedly are a digital currency exchange of course}
by Microsoft regarding used in the Xbox 360 codes system as well as Zune products. Free 4200 microsoft points.
Items could be utilized to obtain video games as well as down-loadable content material material from Xbox 360 system Are living Market place, electronic digital content material like songs as well as video tutorials in Zune Market place, Free 4200 microsoft points together with content material from Windows Are living Gallery
Inside Aug 2013,Free 4200 microsoft points
Ms claimed that it could cycle away Ms Items through the finish connected with 2013, simply utilizing regional money values (such since north america greenback as well as Euro) in the electronic supply programs. A good Xbox 360 elite computer software Xbox 360 codes
package replace applying this kind of change was launched in May twenty six, 2013; users' recent Ms Items will be changed into an comparative volume of regional currency exchange regarding acquisitions. Free 4200 microsoft points rule.Xbox 360 codes.


http://freemicrosoftpoints4200.wordpress.com/

Hash said...

Hi I have deployed this app wart with mentioned script in my new app part page but when i add the app part in my Host web Sender ID is coming blank and Add item button is also not working please help.
I am using office 365 developer site

sarah lee said...

Sincerity and competence is a strong combination. In politics, it is everything. See the link below for more info.


#competence
www.ufgop.org

Post a Comment