SharePoint 2013 Apps: das Chrome Control

In diesem Beitrag soll gezeigt werden, wie man in einer cloud-basierten App (auto-hosted oder provider-hosted) das Chrome Control verwendet. Dieses sorgt dafür, daß die eigene App die von SharePoint 2013 gewohnte Titelleiste verwendet. Außerdem wird dadurch das passende CSS eingebunden, so daß sich die App damit dem Aussehen des Hostwebs anpassen kann. Bei SharePoint-hosted Apps ist das nicht notwendig, weil die Seiten dort als normale Webpartseiten realisiert werden können.

Das wichtigste zu diesem Thema habe ich MSDN entnommen: How to: Use the client chrome control in apps for SharePoint. Ich habe aber das notwendige JavaScript etwas modifiziert und in eine eigene Datei ausgelagert, damit es leichter wiederverwendet werden kann.

Ich gehe hier davon aus, daß die App mit Visual Studio 2012 erstellt wird. Wer die App mit anderen Mitteln baut, kann aber immer noch das notwendige JavaScript gebrauchen.

QueryString erweitern

Wir beginnen zunächst damit die Abfragezeichenfolge (QueryString), die an die App übergeben wird, zu erweitern. Das läßt sich sehr einfach über den AppManifest-Designer erledigen. Visual Studio fügt hier bereits den Platzhalter {StandardTokens} ein, der von SharePoint zur Laufzeit aufgelöst wird und bereits einige wichtige Parameter enthält. Es gibt aber einige mehr und wir wollen hier noch den Titel und das Logo des Hostwebs, damit wir diese ebenfalls im Chrome verwenden können. Das läßt sich erreichen, indem man das hier anhängt: &SPHostTitle={HostTitle}&SPHostLogoUrl={HostLogoUrl}

Weitere Informationen zu den verfügbaren Tokens finden sich hier bei MSDN: URL strings and tokens in apps for SharePoint

Außerdem nutzen wir gleich die Gelegenheit und geben einen weiteren Parameter DisplayType mit, über den wir in der App unterscheiden können, ob eine Seite im FullScreen-Modus oder als AppPart (in einem iframe) aufgerufen wird. Falls eine Seite als AppPart verwendet wird, wollen wir das Chrome Control nicht laden, sondern binden nur die CSS-Datei ein, damit das Styling korrekt funktioniert. Für den DisplayType-Parameter gibt es kein vordefiniertes Token, so daß wir den Wert fest verdrahtet eingeben müssen. Als mögliche Optionen habe ich FullScreen bzw. iframe gewählt.

Insgesamt sieht es dann so aus:

AppWeb erweitern

Unserer App fügen wir jetzt eine zusätzliche JavaScript-Datei hinzu. Ich habe sie appScripts.js genannt und sie kann hier heruntergeladen werden. Wir greifen außerdem auf jQuery zurück, das von Visual Studio aber bereits automatisch zum Projekt hinzugefügt wurde. Der Solution Explorer für das AppWeb sieht dann ungefähr so aus:

JavaScript

Alles was zum Einbinden des Chrome Controls notwendig ist, werden wir hier per JavaScript in der appScripts.js machen. Die fertige Datei kann hier heruntergeladen werden. Ein deklarativer Ansatz ist ebenfalls möglich, aber meiner Meinung nach nicht wirklich wiederverwendbar. Wenn es jemanden interessiert, kann er es im erwähnten Beitrag auf MSDN nachlesen.

Unseren gesamten JavaScript-Code fügen wir in ein eigenes Objekt myApp ein, einfach weil das in JavaScript Best Practices entspricht und den globalen Namensraum nicht "vollmüllt". Wir beginnen also die appScripts.js zu füllen:

"use strict";
var myApp = {
  // code goes here
};

Wir brauchen zum Auslesen der QueryString-Parameter eine möglichst einfache Funktion. Ich habe sie an dieser Stelle schon öfter verwendet und möchte sie deshalb hier nur der Vollständigkeit halber erwähnen. Ich verwende immer das hier:

getQueryStringParameter: function (paramName) {
 
var params = document.URL.split("?")[1].split("&"),
      i,
      singleParam;
  for (i = 0; i < params.length; i++) {
    singleParam = params[i].split("=");
    if (singleParam[0] == paramName) {
      return singleParam[1];
    }
  }
  return "";
}

Wir definieren uns noch eine Variable chromeContainerId, die die ID eines div-Elementes enthält. In dieses div wird dann später das Chrome Control eingesetzt. Weiter unten wird gezeigt, wie man es in die Seite einbaut. Außerdem definieren wir noch ein Array chromeLinks. Diesem können wir einfach weitere Links hinzufügen, die vom Chrome Control gerendert werden. Auch dazu weiter unten mehr. Die Definition als separat zugängliche Variablen habe ich gemacht, damit später immer noch jede Seite die Möglichkeit hat diese Grundeinstellungen zu verändern.

chromeContainerId: "divSPChrome",
chromeLinks: []

Fullscreen oder iframe?

Als nächstes bauen wir eine setupPage-Funktion ein, die von einer Seite aus aufgerufen werden kann und die die ganze Arbeit erledigt. Innerhalb der Funktion holen wir uns zunächst den DisplayType und die SPHostUrl. Außerdem setzen wir uns eine Variable layoutsRoot auf den Layouts-Ordner des Hostwebs, damit wir einfach Elemente von dort referenzieren können:

// Get DisplayType from url parameter
var displayType = decodeURIComponent(myApp.getQueryStringParameter("DisplayType")),
// Get URI decoded SharePoint host site url from the SPHostUrl parameter
spHostUrl = decodeURIComponent(myApp.getQueryStringParameter("SPHostUrl")),
// Build absolute path to the layouts root with the spHostUrl
layoutsRoot = spHostUrl + "/_layouts/15/";

Falls die Seite als AppPart, also in einem iframe, angezeigt werden soll, binden wir kein Chrome Control ein, sondern laden einfach die korrekte CSS-Datei aus dem Hostweb. Das geht über einen dynamisch erzeugten CSS-Link, den wir dem head der Seite hinzufügen:

// Create a Link element for the defaultcss.ashx resource
linkElement = document.createElement("link");
linkElement.setAttribute("rel", "stylesheet");
linkElement.setAttribute("href", layoutsRoot + "defaultcss.ashx");

// Add the linkElement as a child to the head section of the html
headElement = document.getElementsByTagName("head");
headElement[0].appendChild(linkElement);

Wenn die Seite im Fullscreen-Modus angezeigt werden soll, müssen wir zunächst eine SharePoint-Scriptdatei nachladen. Die Datei enthält u.a. das eigentliche Chrome Control. Wir laden die Datei mit der jQuery-Funktion $.getScript und übergeben dabei eine weitere Funktion renderSPChrome. Diese Funktion wird von jQuery aufgerufen, sobald die gewünschte Scriptdatei vollständig geladen ist.

$.getScript(layoutsRoot + "SP.UI.Controls.js", myApp.renderSPChrome);

Jetzt machen wir also mit der renderSPChrome-Funktion weiter. Analog zu oben holen wir uns die SPHostLogoUrl aus dem Querystring:

var hostlogourl = decodeURIComponent(myApp.getQueryStringParameter("SPHostLogoUrl"));

Chrome Control

Als nächstes bauen wir uns ein options-Objekt zusammen. Mit Hilfe dieses Objekts kann man diverse Einstellungen an das Chrome Control übergeben. Die Möglichkeiten sind:

appIconUrl: ein String mit der URL zu einem Bild, das als Anwendungsicon links oben erscheint. Wir verwenden hier einfach das Icon aus dem Hostweb, aber natürlich kann es auch ein eigenes, app-spezifisches Bild sein.

appTitle: ein String mit dem Namen der Anwendung. Er wird rechts neben dem Anwendungsicon und nochmal darüber in der Titelleiste angezeigt. Wir verwenden hier den Titel der Seite, der über document.title erreichbar ist.

appHelpPageUrl: ein String mit der URL zu einer Hilfeseite. Wenn der Parameter angegeben ist, wird ganz rechts oben ein Hilfesymbol mit einem Link zur angegebenen Url eingeblendet. Wie bei allen Urls, die innerhalb der App zur Navigation verwendet werden, sollte man immer die Querystring-Parameter weitergeben, damit sie auf den Folgeseiten ebenfalls zur Verfügung stehen. Das erreicht man mit diesem Code:

"Help.html?" + document.URL.split("?")[1]

onCssLoaded: ein String, der den Namen einer Funktion enthält. Wenn der Parameter angegeben ist, wird diese Funktion aufgerufen, sobald das Chrome Control fertig und alle referenzierten CSS-Dateien geladen sind. Die Funktion kann dann Abschlussarbeiten erledigen, die erst dann ausgeführt werden können oder sollen. Achtung: man muß wirklich einen String angeben. Eine Referenz auf eine Funktion tut es nicht. Der übergebene Parameter wird offenbar per eval() ausgeführt, weshalb man auch die Klammern hinter dem Funktionsnamen () angeben muß. Bei uns sieht das so aus:

"onCssLoaded": "myApp.chromeLoaded()"

settingsLinks: ein Array mit passenden Link-Objekten. Wenn der Parameter angegeben wird, werden diese Links rechts oben als Menü gerendert. Das geschieht an der Stelle, wo sich sonst in SharePoint das Menü befindet, über das z.B. die Websiteeinstellungen zugänglich sind. Die einzelnen Link-Objekte müssen linkUrl und displayName jeweils als Strings enthalten. Die Namen sollten selbsterklärend sein: linkUrl enthält die aufzurufende Url und displayName den angezeigten Text. Das kann z.B. so aussehen:

[
 
{
    "linkUrl": "Account.html?" + document.URL.split("?")[1],
    "displayName": "Account settings"
  },
  {
    "linkUrl": "Contact.html?" + document.URL.split("?")[1],
    "displayName": "Contact us"
  }
]

Über weitere Möglichkeiten der Optionen habe ich noch nichts herausgefunden. Schade, daß Microsoft auch hier wieder auf eine Dokumentation komplett verzichtet 😦

Update 11.09.2013: beim Durchstöbern von SP.UI.Controls.debug.js bin ich auf folgende Möglichkeiten gestossen:

  • siteTitle
  • siteUrl
  • clientTag
  • appWebUrl
  • onCssLoaded
  • assetId
  • appStartPage
  • rightToLeft
  • appTitle
  • appIconUrl
  • appTitleIconUrl
  • appHelpPageUrl
  • appHelpPageOnClick
  • settingsLinks
  • language
  • bottomHeaderVisible
  • topHeaderVisible

Ich habe aber noch nicht alle getestet. Wenn ich mehr über die einzelnen Optionen herausfinde, werde ich obige Aufzählun ergänzen.

Zuletzt müssen wir jetzt noch das eigentliche Chrome Control erzeugen und sichtbar machen:

// Load the Chrome Control in the Chrome Container element of the page
var chromeNavigation = new SP.UI.Controls.Navigation(myApp.chromeContainerId, options);
chromeNavigation.setVisible(true);

Der Vollständigkeit halber hier noch die chromeLoaded-Funktion, die zum Abschluß ausgeführt wird. Wir machen hier nur den body der Seite sichtbar (er ist initial ausgeblendet):

$("body").show();

HTML erweitern

Es folgen noch die Erweiterungen, die im HTML der Seite(n) gemacht werden müssen. Zur Erinnerung: wir verwenden aspx-Seiten, aber grundsätzlich geht es mit jeder anderen Technologie analog. Wir binden zunächst im head jQuery und unsere eigene Scriptdatei ein. Danach rufen wir nur noch eine Funktion auf, sobald die Seite vollständig geladen ist:

<script type="text/javascript" src="../Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="../Scripts/appScripts.js"></script>
<script type="text/javascript">
  $(document).ready(
    myApp.setupPage
  );
</script>

Außerdem brauchen wir ein leeres div mit der oben angesprochenen ID. In dieses div wird das Chrome Control später gerendert und deshalb sollte es als allererstes ganz oben im body stehen:

<body style="display: none;">
 
<form id="form1" runat="server">
    <div id="divSPChrome"></div>

Hier sieht man auch gleich, daß der gesamte body anfangs unsichtbar gesetzt wurde. Zu Testzwecken wurde noch etwas Inhalt eingefügt, bei dem SharePoint-eigene CSS-Klassen verwendet wurden. Die Referenz zu den verfügbaren Klassen findet sich hier: Verwenden des CSS der Hostwebsite in Apps für SharePoint. Man kann daran testen, ob angewendete Designs aus dem Hostweb korrekt übernommen werden:

<h1 class="ms-accentText">H1 Header</h1>
<h2 class="ms-accentText">H2 Header</h2>
<div id="MainContent">
  This is the page’s main content.<br/>
  You can use the links in the header to go back to the host web,
  to help, account or contact pages.
</div>

Zum Schluß noch ein Bild, wie das Ganze aussehen kann:

Und hier nochmal der Downloadlink für die JavaScript-Datei.

6 Gedanken zu “SharePoint 2013 Apps: das Chrome Control

  1. Hi there, rjiang. The Newsletter suicprbstion is what gives you access to the Downloads Section. Please check your spam filter for email from . you should have received a confirmation email with your suicprbstion and the password is included in that message. I just tested the suicprbstion and current password and they appear to be working correctly. Please let me know if your issues persist after receiving the confirmation email. Thank you. The SharePoint Dude.

    Gefällt mir

  2. Hallo Andreas,

    vielen Dank für den ausführlichen Artikel.

    Leider funktioniert deine Javascript Datei bei mir nicht richtig. Wenn ich die App aufrufen will, wirft die Funktion getQueryStringParameter() immer eine Exception
    an der Stelle

    var params = document.URL.split(„?“)[1].split(„&“)

    0x800a138f – JavaScript runtime error: Unable to get property ’split‘ of undefined or null reference

    Wenn ich die Funktionen so wie dem MSDN Artikel beschrieben einbaue, funktioninert zwar der erste Aufruf der App, jeder weitere Aufruf der Startseite und aller weiteren Seiten werfen aber die gleiche Exception.

    Weißt du zufällig, woran das liegen könnte?

    Gruß

    Max

    Gefällt mir

  3. Hallo Max,

    die Funktion getQueryStringparameter dient zum Auslesen von Parametern aus der URL.
    Bei Dir scheint document.URL „undefined“ zu sein, was meines Wissens nur sein kann, wenn man mit Frames (oder iframes) arbeitet. Versuche doch mal window.location.href, das sollte eigentlich auch funktionieren.

    Das Problem bei den weiteren Seitenaufrufen ist, daß die URL-Parameter nicht weitergereicht werden. Wenn es keine Parameter gibt, wirft die Funktion einen Fehler. Innerhalb von Apps bist Du immer selbst dafür verantwortlich diese Parameter weiterzureichen oder zwischenzuspeichern.

    Viele Grüße
    Andi

    Gefällt mir

  4. Danke für die schnelle Antwort!

    Das Problem liegt wirklich bei den nicht weitergegeben URL-Parametern. Wenn ich diese per Hand eintrage gibt es keine Probleme.

    Gibt es irgendeine Standardlösung für Sharepoint Apps um die Parameter weiterzugeben?

    Gefällt mir

  5. Hallo Max,

    nein, es gibt keine Standardlösung, nur unzählige Möglichkeiten 😉

    Z.B. alle Parameter per JS auslesen und wieder an alle Links auf der Seite anhängen. Oder eben irgendwo zwischenspeichern (Cookie, DB, …)

    Viele Grüße
    Andi

    Gefällt mir

  6. Danke!

    Ich hatte mich inzwischen schon für deinen ersten Vorschlag entschieden und hänge die Parameter an alle Links dran.

    Funktioniert gut, trotzdem hatte ich gehofft, dass einem da noch einfachere Mittel an die Hand gegeben werden.

    Es kommt ja sicher nicht so selten vor, dass eine App Chrome Control nutzt, aus mehr als einer Seite besteht und die Seiten untereinander verlinkt sind. 😉

    Gruß

    Max

    Gefällt mir

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s