SharePoint 2013: Liste innerhalb der Websitesammlung anzeigen

Eine Frage, die immer wieder aufkommt, ist die, wie man die Daten einer Liste oder einer Bibliothek in einer anderen Website anzeigen kann. In einem früheren Beitrag habe ich gezeigt, wie man das in SharePoint 2010 machen kann. Leider funktioniert es in SharePoint 2013 mit der Methode nicht mehr. Die Vorgehensweise ist zwar ähnlich, aber auch die Werkzeuge haben sich verändert, so daß es jetzt einen eigenen Beitrag dazu gibt, wie man eine Listenansicht in SharePoint 2013 für die gesamte Websitesammlung zur Verfügung stellt.

Ansicht vorbereiten

Man kann die Ansicht zwar auch später noch problemlos über die Webparteinstellungen bearbeiten, auch dann, wenn sie in einer anderen Website eingefügt wurde, aber es empfiehlt sich trotzdem sie bereits an der Quelle so aufzubereiten, wie sie später an den meisten Stellen verwendet wird (oder verwendet werden soll).

Man kann dazu entweder eine vorhandene Listen- oder Bibliotheksansicht verändern, auf der Liste oder Bibliothek eine neue Ansicht erzeugen (und später wieder löschen) oder auch auf einer beliebigen Seite eine Ansicht einfügen und verändern. Auch in diesem Fall kann man sie später problemlos wieder von der Seite entfernen (oder die ganze Seite löschen).

Ansicht exportieren

Die oben erstellte Ansicht muß jetzt exportiert werden. Warum Microsoft den dafür vorgesehenen Menüpunkt aus dem Webpartmenü entfernt hat, kann ich nicht sagen, aber mit SharePoint Designer geht es zum Glück immer noch. Man öffnet also die Website in SharePoint Designer und navigiert zur Seite mit der gewünschten Ansicht. Wenn sie sich als Ansicht direkt auf der Liste oder Bibliothek befindet, kann man dazu über Listen und Bibliotheken und dann über die Ansichten einer Liste oder Bibliothek gehen. Wenn sie sich auf einer beliebigen Seite befindet, muß man diese öffnen. Je nach Speicherort der Seite kann man z.B. über Site Pages oder auch über Alle Dateien dorthin gelangen.

In SharePoint Designer 2013 gibt es keine Designansicht mehr, so daß man nach dem Öffnen der Seite direkt in die Quellcodeansicht gelangt. Dort sucht man sich die gewünschte Ansicht (im Idealfall befindet sich nichts anderes auf der Seite). Man findet Listenansichten z.B. indem man nach WebPartPages:XsltListViewWebPart sucht. Jetzt setzt man einfach die Einfügemarke irgendwo innerhalb dieses Elements und im Ribbon oben erscheint unter der Überschrift WEBPARTTOOLS der Reiter FORMAT. Darin gibt es rechts zwei Optionen zum Webpart speichern:

Direktes Speichern in den Websitekatalog hat keinen Sinn, weil es damit in anderen Websites nicht funktionieren wird. Man speichert das Webpart also in eine Datei, z.B. als ListeInWebsite1.webpart und merkt sich den Ort, an dem man die Datei abgelegt hat.

Gespeicherte Ansicht editieren

In der Datei muß jetzt eine Änderung gemacht werden. Es handelt sich dabei um eine XML-Datei, die man mit einem geeigneten Editor bearbeiten kann. Zur Not geht es auch mit Windows-Notepad. Man sucht in der Datei nach WebId und stellt fest, daß diese als leere Guid hinterlegt wurde: 00000000-0000-0000-0000-000000000000

Dort muß jetzt die richtige ID der Website eingetragen werden, in der sich die Liste oder Bibliothek tatsächlich befindet. Man findet diese ID z.B. über den REST-Service, indem man im Browser an die Adresse der Website folgendes anhängt: /_api/web/id

Als Adresse steht dann sowas im Browser http://sharepoint/sites/web2/_api/web/id und die Ausgabe sieht so aus:

Der markierte Teil ist die gesuchte Guid der Website. Man überschreibt damit die leere Guid in der Datei und speichert sie. Falls der Browser die Daten nicht wie oben gezeigt darstellt: hier steht, wie man das ändert.

Ansicht importieren

Die in der Datei gespeicherte Ansicht wird jetzt in den Webpartkatalog importiert. Dazu geht man zuerst in die Rootwebsite der Websitesammlung. Dort dann über das Menü hinter dem kleinen Zahnrad rechts oben in die Websiteeinstellungen. Unter Web-Designer-Kataloge findet man den Link Webparts. Dort lädt man die Datei hoch und kann dann noch verschiedene Metadaten angeben wie z.B. eine Beschreibung. Man kann auch die Gruppe angeben, unter das Webpart später beim Einfügen in eine Seite gefunden wird. Wenn man hier nichts angibt, erscheint es unter Verschiedenes.

Das war’s auch schon. Die Ansicht ist jetzt als wiederverwendbares Webpart im Webpartkatalog gespeichert und kann innerhalb der gesamten Websitesammlung verwendet werden.

Dynamische Titel bei benutzerdefinierten Formularen

Wenn man mit SharePoint Designer benutzerdefinierte Formulare anlegt, wird für den Titel, also für den Namen der Spalten, immer der aktuelle Wert fest eingetragen. Für den eigentlichen Feldwert, also für den Teil mit dem die Benutzer später interagieren (z.B. eine Textbox oder Checkbox) wird ein FieldControl eingefügt. Ebenso für den Beschreibungstext einer Spalte. Wenn man dann später etwas ändert, indem man z.B. bei einem Auswahlfeld von DropDown auf Radiobuttons umschaltet, dann sind diese Änderungen auch sofort in den benutzerdefinierten Formularen zu sehen. Wenn man aber den Spaltennamen ändert, wird im benutzerdefinierten Formular immer noch der alte Name angezeigt. Außerdem wird dieser Name immer nur in einer Sprache angezeigt und paßt sich nicht an, wenn ein Benutzer eine andere Sprache auswählt.

Um dieses Problem zu lösen, kann man in der Codeansicht von SharePoint Designer den fest eingefügten Text durch ein in SharePoint immer vorhandenes Control ersetzen:

<SharePoint:FieldLabel ControlMode="Display" runat="server" FieldName="Title" />

Für das FieldName-Attribut gibt man wie immer den internen Namen der Spalte an. Das ControlMode-Attribut muß angegeben werden, aber es macht keinen Unterschied, ob man Display, Edit oder New einträgt.

Das war’s auch schon. Es ist mir (mal wieder) ein Rätsel, warum SharePoint Designer das nicht gleich so macht.

List-Joins und Projected Fields

In einem älteren Beitrag habe ich gezeigt, was mit CAML-Abfragen grundsätzlich möglich ist. Als Ergänzung dazu soll dieser Beitrag zeigen, wie man mit solchen Abfragen zwei oder mehr Listen verbinden kann, analog zum SQL JOIN-Statement. Man erhält dadurch mit einer einzigen Abfrage eine Ergebnisliste, die Daten aus mehreren Listen enthält. Der Mechanismus funktioniert allerdings nur, wenn die Listen durch Nachschlagefelder miteinander verbunden sind.

Motivation

Bei einem Nachschlagefeld kann man zwar weitere Felder der Nachschlageliste auswählen, die dann ebenfalls in Ansichten der Liste dargestellt werden, aber das funktioniert nur mit einer Ebene. Es funktioniert nicht, wenn Daten aus mehr als zwei Listen benötigt werden.

Und es funktioniert nur mit Standard-Nachschlagespalten. Es gibt im Web mehrere Implementierungen eigener Feldtypen, die Nachschlagefelder erweitern, z.B. um ein gefiltertes oder kaskadierendes Nachschlagen zu ermöglichen oder um das Nachschlagefeld mit einem Picker-Dialog auszustatten. Die meisten dieser Implementierungen sind vom Standard-Nachschlagefeld abgeleitet und bieten von Haus aus nicht mehr die Möglichkeit weitere Felder darzustellen. Weil die Daten aber genau wie bei den Standardfeldern gespeichert werden, können diese Felder für CAML List-Joins herangezogen werden.

Umgebung

Für die Demonstration habe ich folgende Umgebung eingerichtet: eine Liste mit Projekten und den Feldern Projektname, Projektnummer und geplantes Projektende. Und eine weitere Liste Projektzeiten, in der die Benutzer ihre Arbeitszeiten eintragen können, die sie jeweils für ein Projekt aufgewendet haben. Diese Liste hat die Felder Datum, Stunden, ein Bemerkungsfeld und natürlich ein Nachschlagefeld auf die Projekte.

Das ist die Projektliste:

Und das die Liste mit den Projektzeiten:

Für eine Übersicht sollen jetzt alle erfaßten Zeiten mit zusätzlichen Informationen aus der Projektliste dargestellt werden.

So geht’s

Man legt für die Liste Projektzeiten eine neue Ansicht an und öffnet die Website in SharePoint Designer. Wenn man über Listen und Bibliotheken auf die Liste navigiert, findet man rechts die Ansichten dieser Liste und kann diese neue Ansicht öffnen. Man markiert in der Entwurfsansicht die Tabelle und schaltet in die Codeansicht um. Jetzt sieht man die CAML-Abfrage, die dieser Ansicht zugrunde liegt. Sie sieht leicht gekürzt ungefähr so aus:

<View>
  <Query>
    <OrderBy>
      <FieldRef Name=“Projekt“/>
    </OrderBy>
  </Query>
  <ViewFields>
    <FieldRef Name=“Projekt“/>
    <FieldRef Name=“Datum“/>
    <FieldRef Name=“Stunden“/>
    <FieldRef Name=“Author“/>
  </ViewFields>
  <RowLimit Paged=“TRUE“>100</RowLimit>
  <Aggregations Value=“Off“/>
  <Toolbar Type=“Standard“/>
</View>

Diese Abfrage wird jetzt editiert. Zunächst verbindet man die Listen durch ein Join-Element, das direkt als Kind des View-Elements notiert wird:

<Joins>
<Join Type=“INNER“ ListAlias=“Projekte“>
    <Eq>
      <FieldRef Name=“Projekt“ RefType=“Id“/>
      <FieldRef List=“Projekte“ Name=“ID“/>
    </Eq>
  </Join>
</Joins>

Als Join-Type kann dabei INNER oder LEFT angegeben werden. Das Ganze funktioniert analog zum bekannten JOIN-Statement in SQL.

Als ListAlias kann ein beliebiger Name gewählt werden. Dieser Name wird später bei den ProjectedFields verwendet (s.u.). Der ListAlias ist außerdem zur Unterscheidung wichtig, falls eine Liste mehrere Nachschlagefelder auf dieselbe Liste hat. Man denke z.B. an eine Liste mit Adressen, aus der sowohl eine Rechnungs- als auch eine Lieferadresse ausgewählt werden soll. Die Liste selbst muß nicht explizit angegeben werden. Sie wird über die Definition der verknüpfenden Felder identifiziert (s.u.).

Als Vergleichsoperator muß immer Eq angegeben werden.

Der Vergleichsoperator Eq enthält immer zwei FieldRef-Elemente, die die Verknüpfung definieren. Das erste stellt dabei immer das Nachschlagefeld der primären Liste dar, das (wie immer in CAML) durch seinen internen Namen referenziert wird. Als RefType wird immer Id angegeben. Falls die primäre Liste der Verknüpfung nicht die eigentliche Liste ist, für die diese Ansicht erstellt wird, z.B. weil es eine weitere Verbindung der Projekte zu einer Kundenliste gibt, muß in einem zusätzlichen List-Attribut auch die Liste angegeben werden. Das zweite FieldRef-Element gibt die Liste an, auf die nachgeschlagen wird. Als Name wird immer ID angegeben.

Damit ist die Verbindung der Listen grundsätzlich definiert. Als nächstes müssen alle Felder der verknüpften Listen deklariert werden, die in irgendeiner Form in der Abfrage verwendet werden sollen, egal ob zur Anzeige, Sortierung oder als Filter. Das geschieht in einem ProjectedFields-Element:

<ProjectedFields>
<Field Name=“Projektname“ Type=“Lookup“ List=“Projekte“ ShowField=“Title“/>
  <Field Name=“Projektnummer“ Type=“Lookup“ List=“Projekte“ ShowField=“Projektnr“/>
  <Field Name=“Projektende“ Type=“Lookup“ List=“Projekte“ ShowField=“GeplEnde“/>
</ProjectedFields>

Jedes Field-Element gibt im Name-Attribut einen Namen an, unter dem das Feld an anderen Stellen der Abfrage identifiziert wird. Der Name wird außerdem bei angezeigten Feldern als Spaltenüberschrift verwendet. Leider muß dieser Name XML-konform sein, so daß das einzige verwendbare Sonderzeichen der Unterstrich ist. Alle anderen nicht-alphanumerischen Zeichen wie Leerzeichen, Punkt oder Komma sind nicht erlaubt.

Das Type-Attribut wird immer mit Lookup belegt.

Das List-Attribut enthält den ListAlias, der oben beim Join (s.o.) angegeben wurde.

Das ShowField-Attribut enthält wiederum den internen Namen des Feldes aus der Nachschlageliste.

Damit stehen die Felder aus der Nachschlageliste in der Abfrage zur Verfügung und können z.B. angezeigt werden. Man kann sie auch problemlos zum Sortieren oder als Filter in der Where-Bedingung verwenden. Sie werden dabei wie gewohnt als FieldRef-Elemente notiert. Als Name wird dabei der oben bei den ProjectedFields angegebene Name verwendet.

Zur Anzeige der Felder werden diese bei den ViewFields notiert:

<ViewFields>
  <FieldRef Name=“Projekt“/>
  <FieldRef Name=“Projektnummer“/>
  <FieldRef Name=“Projektende“/>
  <FieldRef Name=“Datum“/>
  <FieldRef Name=“Stunden“/>
  <FieldRef Name=“Author“/>
</ViewFields>

Die Ansicht der Projektzeiten sieht dann so aus:

Erweiterungen

Wie oben bereits angedeutet, ist dieser Mechanismus nicht auf zwei Listen beschränkt. Es lassen sich damit Daten aus vielen Listen in einem Rutsch abfragen, solange die Listen über Nachschlagefelder verbunden sind.

In unserem Beispiel wäre es denkbar, daß die Projektliste ein Nachschlagefeld auf eine weitere Liste Kunden enthält, mit dem jedes Projekt einem Kunden zugeordnet wird. Man kann dann in einer Ansicht sowohl die erfaßten Projektzeiten als auch die Daten des Projekts und des zugehörigen Kunden (z.B. seine Adresse) anzeigen.

Datumsvergleiche für bedingte Formatierungen in Datenansichten

Leider mußte ich eben feststellen, daß bedingte Formatierungen mit Datumsvergleichen in einer Datenansicht mit dem XsltListViewWebPart bei deutschen Ländereinstellungen nicht funktionieren.

Wenn man zum Beispiel die Zeilen einer Aufgabenliste je nach Status (abgeschlossen oder nicht) und Fälligkeitsdatum unterschiedlich einfärben möchte, dann funktioniert das nur wenn in der entsprechenden Website ein englisches Datumsformat (MM/DD/YYYY) eingestellt ist. Abweichungen davon, z.B. beim deutschen Format DD.MM.YYYY, sorgen dafür, daß die automatisch erzeugten Datumsvergleiche manchmal fehlschlagen. Ja, richtig: manchmal.

Update: das Problem wurde durch Service Pack 1 behoben.

Wenn man sich im SharePoint Designer 2010 eine bedingte Formatierung "zusammenklickt"…

…dann wird dieser XSL-Code erzeugt:

<xsl:if test="ddwrt:DateTimeTick(ddwrt:GenDisplayName(string($thisNode/@DueDate))) &lt; ddwrt:DateTimeTick(ddwrt:GenDisplayName(string(‚Today‘)))" …

Wie man sieht wird dabei mit der Funktion ddwrt:DateTimeTick gearbeitet (sie liefert die Anzahl Tage seit 01.01.1900). Ein paar Versuche haben gezeigt, daß diese Funktion nur für ein englisches Datum das richtige Ergebnis liefert. Wenn sich ein deutsches Datum englisch interpretieren läßt (z.B. der 07.04. als 4. Juli), wird ein falsches Ergebnis geliefert. Wenn sich ein deutsches Datum gar nicht englisch interpretieren läßt (z.B. der 13.04. als 4. Des dreizehnten Monats), wird einfach 0 (Null) geliefert. Das ist die Erklärung dafür, daß es eben manchmal funktioniert.

Ich habe dann Versuche mit den Funktionen ddwrt:FormatDate und ddwrt:FormatDateTime angestellt. Eigentlich wollte ich damit ein Datum im deutschen Format ins englische Format übertragen und dadurch erreichen, daß die Datumsvergleiche immer korrekt funktionieren. Leider wurde es von Microsoft bis heute nicht für notwendig befunden, die ddwrt-Erweiterungen zu dokumentieren. Die einzige Dokumentation dazu ist vom Oktober 2005 und bezieht sich auf SharePoint 2003. Sehr schade, da diese Erweiterungsfunktionen durchaus nützlich sind bzw. sein könnten.

Wie auch immer: es ist mir nicht gelungen, ein deutsches Datum ins englische Format zu übertragen. Nach ein paar weiteren Versuchen habe ich mich für folgende Lösung entschieden:

<xsl:if test="number(concat(substring(string($thisNode/@DueDate),7,4),substring(string($thisNode/@DueDate),4,2),substring-before(string($thisNode/@DueDate),‘.‘))) &lt; translate(substring-before(ddwrt:TodayIso(),’T‘),‘-‚,“) …

Dieser Vergleich macht genau dasselbe wie der oben gezeigte, funktioniert aber für das deutsche Datumsformat (allerdings nur für das).

Erklärung:

Beide Daten (Fälligkeitsdatum und aktuelles Datum) werden ins Format YYYYMMDD gebracht und das dann verglichen. Mit kleinen Änderungen, sollte sich dieses Vorgehen auf jedes beliebige Datumsformat umbauen lassen. Das Fälligkeitsdatum wird mit Stringfunktionen in seine Bestandteile zerlegt und in der richtigen Reihenfolge ohne Trennzeichen zusammengesetzt. Das aktuelle Datum erhält man von der ddwrt:TodayIso-Funktion bereits in der richtigen Reihenfolge. Es wird einfach alles nach dem ‚T‘ abgeschnitten und das Trennzeichen-Minus entfernt.

Änderungen am XSLT einer Datenansicht werden nicht übernommen

Ich habe mich gerade ziemlich lange mit einem sehr seltsamen Problem in SharePoint Designer 2010 herumgeschlagen: wenn man bei einem XsltListViewWebPart Änderungen am XSLT vornimmt, werden diese zwar in der Entwurfsansicht in SharePoint Designer korrekt angezeigt, aber nach dem Speichern sind die Änderungen nur sporadisch im Browser sichtbar. Seltsamerweise werden diese Änderungen zwar meistens, aber eben nicht immer unterschlagen. Lösung: man entfernt alle ddwrt:ghost-Attribute.

Vorgehensweise:

Damit man überhaupt etwas an der Standardansicht eines XsltListViewWebPart ändern kann, muß man zunächst das XSLT zugänglich machen. Wenn man die Seite im SharePoint Designer geöffnet und das Webpart markiert hat, geht das durch Klick auf XSLT anpassen im Reiter Entwurf:

Man kann dort auswählen, ob man nur das Template für das ausgewählte Element, also z.B. eine Tabellenzelle, oder das gesamte XSLT verändern möchte.

Jetzt kann man die Ansicht nach Belieben verändern. Man kann vieles über die vom Designer gebotenen Möglichkeiten machen, wie z.B. einzelnen Spalten eine andere Hintergrundfarbe geben, man kann bedingte Formatierungen anwenden und Schriftarten und -größen ändern. Wer möchte kann auch durch direktes Editieren im XSLT-Code die gesamte Ansicht umgestalten.

Allen diesen Änderungen ist aber gemein, daß sie zwar im Entwurfsfenster von SharePoint Designer korrekt angezeigt werden, aber wenn man sie nach dem Speichern im Browser betrachten will, wird man meist enttäuscht. Die Änderungen werden fast immer beim Speichern unterschlagen. Nach langem Suchen und verzweifeltem Haareraufen habe ich die Lösung dieses Problems gefunden: im Code findet sich an vielen Stellen ein ddwrt:ghost="hide"-Attribut. Wenn man es leer macht (ddwrt:ghost="") oder gleich ganz entfernt, funktioniert alles wie gewünscht. Weil dieses Attribut mehr als 60 Mal im Code vorkommt, bietet sich dafür die Suchen&Ersetzen-Funktion an (Strg+H).

Falls jemand weiß, wozu dieses Attribut da ist oder ob es überhaupt einen Sinn hat, möge er mir das bitte mitteilen. Ich konnte bisher jedenfalls keinen finden.

Eine Liste innerhalb der Websitesammlung anzeigen

Eine Frage, die immer wieder aufkommt, ist die, wie man die Daten einer Liste oder einer Bibliothek in einer anderen Website anzeigen kann.

Bei SharePoint 2007 gab es in der Datenquellenbibliothek von SharePoint Designer 2007 den Link "Mit einer anderen Website verbinden". Damit konnte man sich mit jeder Website innerhalb der Websitesammlung verbinden und dann ganz einfach die Daten aller dort vorhandenen Listen und Bibliotheken anzeigen lassen. Diese Möglichkeit besteht jetzt zwar nicht mehr, aber es ist trotzdem nicht schwierig.

Man geht dazu auf eine beliebige Webpart-Seite der Website, in der sich die Liste oder Bibliothek befindet. Man kann dazu auch einfach die default.aspx verwenden. Man versetzt die Seite in den Bearbeitungsmodus…

…und fügt dann das Webpart der Liste oder Bibliothek ein, das man unter "Listen und Bibliotheken" findet.

Natürlich kann man zum Einfügen des Webparts auch den SharePoint Designer verwenden. Wenn man eine Webpart-Seite geöffnet hat, findet man alle Listen und Bibliotheken im Reiter "Einfügen" unter "Datenansicht".

Das Aussehen des Webparts kann man jetzt nach Belieben verändern. Sobald man damit zufrieden ist, öffnet man die Seite im SharePoint Designer (falls noch nicht geschehen) und markiert das Webpart. Im Reiter "Webpart" findet man oben rechts die Funktion "In Websitekatalog":

Wenn man darauf klickt, erscheint ein Dialog, bei dem man einen Namen und optional eine Beschreibung angeben kann. Voreingestellt ist der Name der Liste oder Bibliothek. Unter diesem Namen erscheint das Webpart später im Webpartkatalog. Wenn man den Dialog mit "OK" bestätigt, erscheint folgende Messagebox:

Hier muß unbedingt auf "Ja" geklickt werden, sonst funktioniert das Webpart später nicht wie erwartet!

Das war’s auch schon. Die Ansicht ist jetzt als wiederverwendbares Webpart im Webpartkatalog gespeichert und kann innerhalb der gesamten Websitesammlung verwendet werden. Man findet es in der Kategorie "Custom" oder "Benutzerdefiniert", aber das läßt sich im Webpartkatalog ändern.

Das Webpart, das weiter oben eingefügt wurde, kann jetzt getrost wieder entfernt werden. Es diente nur dazu, die gewünschte Ansicht in den Webpartkatalog speichern zu können und ist nicht mehr länger notwendig.