Link zum Erstellen eines Detaildatensatzes

Wenn man zwei 1:n-verbundene Listen hat, wird oft ein Link gewünscht, über den man einen Detaildatensatz erstellen kann und bei dem das Nachschlagefeld zum zugehörigen Elterndatensatz bereits vorbelegt ist. Hier wird gezeigt wie man das mit Hilfe von etwas JavaScript umsetzen kann.

Das grundsätzliche Vorgehen dabei ist folgendes: man erstellt einen eigenen Link, mit dem ausgehenden von einem Elternelement ein neues Detailelement erstellt werden kann. Der Link verweist dabei auf das Formular zur Neuanlage der Detailliste und enthält als zusätzlichen Parameter die ID des Elternelements. In das Formular zur Neuanlage wird JavaScript eingebaut, das diesen Parameter wieder aus der URL abholt und das Nachschlagefeld damit vorbelegt. Falls gewünscht kann man das Nachschlagefeld dann auch ausblenden.

Ausgangslage

Es gibt eine Parentliste, die hier zu Demozwecken ganz einfach aufgebaut ist und nur die Titelspalte enthält:

Dazu gibt es eine Detailliste, die ebenso einfach aufgebaut ist und außer dem Titel nur ein Nachschlagefeld auf die Parentliste enthält:

Man hat dadurch, ähnlich wie bei einem klassischen relationalen Datenmodell, eine 1:n-Verbindung zwischen den Listen. Jedem Elterndatensatz können mehrere Kinddatensätze zugeordnet sein. Jeder Kinddatensatz ist genau einem Elterndatensatz zugeordnet. In der Praxis wird so etwas z.B. bei Rechnungen (ein Datensatz Rechnungskopf mit Rechnungsnummer, Anschrift usw. und mehrere Datensätze Rechnungspositionen mit Artikelnummer, Preis usw.) oder bei Kontaktdaten (ein Datensatz Firma mit Kundennummer, Anschrift usw. und mehrere Datensätze Ansprechpartner mit Name, Telefonnummer usw.) verwendet.

Darstellung

Um die Daten jetzt immer schön im Zusammenhang darzustellen, kann man sehr einfach zu jedem Elterndatensatz die zugehörigen Kinddatensätze anzeigen lassen. Man fügt dazu einfach eine Ansicht der Detailliste auf das Anzeige- und das Ändern-Formular (DispForm und EditForm) der Elternliste ein. SharePoint sorgt dafür, daß diese Ansicht korrekt gefiltert wird.

Zum Einfügen geht man so vor: man geht auf die Elternliste, klickt auf Liste, öffnet den Menüpunkt Formularwebparts ändern und wählt das anzupassende Formular aus:

 

Die Menüpunkte der einzelnen Formulare sind meiner Meinung nach etwas unglücklich übersetzt. Neues Standardformular meint das Formular zur Neuanlage (NewForm.aspx), Standardanzeigeformular meint das Formular zur Anzeige (DispForm.aspx) und Standardformular bearbeiten meint das Formular zum Ändern (EditForm.aspx).

Wenn das gewünschte Formular geöffnet ist, klickt man irgendwo in die Seite, damit der Reiter Einfügen sichtbar wird. Dort gibt es dann einen Menüpunkt Verwandte Liste, der beim Aufklappen alle Listen anzeigt, die ein Nachschlagefeld auf die aktuelle Liste enthalten:

 

Man wählt die einzufügende Liste aus und es wird eine automatisch gefilterte Ansicht dieser Liste eingefügt. Die Ansicht kann jetzt wie gewohnt über die Webparteinstellungen angepaßt werden (Menüpunkt Webpart bearbeiten). Für uns ist hier wichtig den standardmäßig unter der Ansicht angezeigten Link Neues Element hinzufügen zu entfernen, damit wir ihn durch einen eigenen Link ersetzen können. Damit der Link nicht mehr angezeigt wird, setzt man in den Webparteinstellungen den Symbolleistentyp auf Keine Symbolleiste:

Der Neues Element erstellen Link

Um den ausgeblendeten Link durch einen eigenen zu ersetzen, gibt es zwei Möglichkeiten: man kann das Formular in SharePoint Designer öffnen und die Änderungen dort direkt in der Codeansicht machen. Oder man fügt im Browser noch ein Inhalts-Editor-Webpart ein und bearbeitet das in der HTML-Ansicht. Dieser Weg wird hier gezeigt.

Das Webpart findet sich in der Kategorie Medien und Inhalt und man fügt es am Sinnvollsten direkt unter der neu eingefügten Ansicht ein. Eventuell muß man es dazu im Browser nach unten verschieben. Nachdem man in das Webpart geklickt hat, wählt man im Reiter Text formatieren aus dem Menü HTML den Punkt HTML-Quelle bearbeiten:

Wir brauchen zunächst eine Funktion, die uns einen bestimmten Parameter aus der URL liefert:

function getQueryStringParameter(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 "";

}

Diese Funktion benutzen wir, um die ID aus der URL zu holen:

var lookupId = getQueryStringParameter("ID");

Damit setzen wir uns die URL zusammen, über die wir das Formular zur Neuanlage eines Kindelements aufrufen:

var newFormUrl = "/site/Lists/ChildList/NewForm.aspx?LookupId=" + lookupId;

Wenn man möchte, daß der Benutzer nach dem Speichern des neuen Elements (oder nach einem Klick auf Abbrechen) wieder auf die ursprüngliche Seite zurückgeleitet wird, kann man die dafür in SharePoint vorgesehene Standardtechnik benutzen und einen zusätzlichen Source-Parameter mitgeben. Der Parameter muß die Adresse der ursprünglichen Seite und zusätzlich wieder die ID als Parameter enthalten. Man kann das durch folgende Zeile erreichen:

newFormUrl += "&Source=" + encodeURI(window.location.pathname + "?ID=" + lookupId);

Diese URL benutzen wir, um einen vorher definierten Anker mit dem korrekten Link zu versorgen. Außerdem packen wir das Ganze in eine Funktion, damit man es nach dem vollständigen Laden der Seite aufrufen kann:

function createNewLink() {

  var lookupId = getQueryStringParameter("ID"),

      newFormUrl = "/site/Lists/ChildList/NewForm.aspx?LookupId=" + lookupId,

      link = document.getElementById("myNewLink");

  newFormUrl += "&Source=" + encodeURI(window.location.pathname + "?ID=" + lookupId);

  link.href = newFormUrl;

}

Achtung: dieses Verfahren funktioniert nur, wenn das aktuelle Formular nicht in einem Dialog geöffnet wurde. Dialoge kann man in den Listeneinstellungen unter Erweiterte Einstellungen abschalten. Eleganter ist es natürlich, wenn es auch von einem Dialog aus funktioniert und wenn das Formular zum Erstellen des neuen Elements ebenfalls in einem Dialog geöffnet wird.

Das zu erreichen ist aber relativ einfach: man gibt dem Anker einfach ein zusätzliches onclick-Attribut und ruft darin die von SharePoint vorgesehene Funktion auf:

link.onclick = function (event) { NewItem2(event, newFormUrl); return false; };

Dabei muß man der NewItem2-Funktion allerdings die absolute URL übergeben, die man so erzeugen kann:

newFormUrl = window.location.protocol + "//" + window.location.hostname + newFormUrl;

Hier nochmal zusammengefaßt der gesamte Code zum Kopieren und Einfügen in die HTML-Quelle des Webparts. Nicht vergessen, die newFormUrl auf die eigene Umgebung anzupassen!

<a href="#" id="myNewLink">Neues Element erstellen</a>

<script type="text/javascript">

_spBodyOnLoadFunctionNames.push("createNewLink");

function createNewLink() {

  var lookupId = getQueryStringParameter("ID"),

      newFormUrl = "/site/Lists/ChildList/NewForm.aspx?LookupId=" + lookupId,

      link = document.getElementById("myNewLink");

  link.href = newFormUrl;

  newFormUrl = window.location.protocol + "//" + window.location.hostname + newFormUrl;

  link.onclick = function (event) { NewItem2(event, newFormUrl); return false; };

}

function getQueryStringParameter(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 "";

}

</script>

NewForm anpassen / Nachschlagefeld vorbelegen

Mit dem oben Gezeigten haben wir jetzt also einen Link auf das Formular, mit dem die neuen Kindelemente erzeugt werden. Die ID des Elternelements wird dabei in der URL übertragen. Kommen wir jetzt also zu den Anpassungen, die man dort noch machen muß, um diese ID auszulesen und das Nachschlagefeld damit vorzubelegen.

Wie man den notwendigen JavaScript-Code auf ein Listenformular bekommt, habe ich ja oben schon beschrieben und werde deshalb hier nicht weiter darauf eingehen.

Um an die ID des Elternelements zu kommen, verwenden wir wieder die oben gezeigte Funktion getQueryStringParameter. Spätestens jetzt sollte man sich überlegen, diese Funktion in eine eigene js-Datei zu packen, damit man sie einfach wiederverwenden kann.

Außerdem brauchen wir eine Hilfsfunktion getTagFromIdentifierAndTitle, die tausendfach durchs Web geistert. Ich verzichte hier auf eine Quellenangabe, da ich ohnehin nicht weiß, wer sie ursprünglich erdacht hat. Sie liefert ein HTML-Element anhand des Tagnamens, einem optionalen Feldtyp und dem Feldnamen:

function getTagFromIdentifierAndTitle(tagName, identifier, title) {

  var idLength = identifier.length,

      tags = document.getElementsByTagName(tagName),

      i,

      tagID;

  for (i = 0; i < tags.length; i++) {

    tagID = tags[i].id;

    if (tags[i].title == title && (identifier == "" || tagID.indexOf(identifier) == tagID.length – idLength)) {

      return tags[i];

    }

  }

  return null;

}

Diese Funktion können wir jetzt benutzen, um an das Nachschlagefeld zu gelangen. Hier kommt aber eine besondere Verhaltensweise von Nachschlagefeldern hinzu, der besondere Beachtung geschenkt werden muß. Wenn die Nachschlageliste weniger als 20 Elemente enthält, wird das Nachschlagefeld als ganz normales <select> gerendert. Man muß dann einfach nur das gesuchte <option> anhand seines Values finden und auswählen. Dazu dient diese Funktion:

function setSelectedOption(select, value) {

  var opts = select.options,

      optLength = opts.length,

      i;

  for (i = 0; i < optLength; i++) {

    if (opts[i].value == value) {

      select.selectedIndex = i;

      return true;

    }

  }

  return false;

}

Wenn die Nachschlageliste mehr Elemente enthält, wird für das Nachschlagefeld ein komplizierteres DHTML-Konstrukt gerendert. Das Haupteingabefeld ist dabei ein <input type="text">. Der gespeicherte Wert befindet sich allerdings in einem <input type="hidden"> und dieses wiederum findet man über seine ID, die beim Eingabefeld in einem optHid-Attribut gespeichert ist. Puh.

Da das Feld später vorbelegt und sinnvollerweise von den Benutzern nicht geändert werden soll, blenden wir es aus. Wir wollen dabei nicht nur das Feld selbst ausblenden, sondern gleich die gesamte Tabellenzeile, in der es sich befindet. Auch dabei müssen die beiden Arten unterschieden werden. Beim <select> geht es drei Ebenen nach oben, bis man die Tabellenzeile erreicht. Beim <input> sind es vier Ebenen.

Achtung: man darf die Felder nicht auf disabled oder ähnliches setzen, weil sonst ihre Werte beim POST nicht übertragen und damit nicht gespeichert werden!

Damit können wir uns jetzt eine wiederverwendbare Funktion bauen, die den Wert eines Nachschlagefeldes unabhängig von der Anzahl der Nachschlageelemente setzt und auch gleich das Feld ausblendet:

function setLookupField(fieldName, value) {

  var theSelect = getTagFromIdentifierAndTitle("select", "", fieldName),

      theInput;

  if (theSelect == null) {

    theInput = getTagFromIdentifierAndTitle("input", "", fieldName);

    document.getElementById(theInput.optHid).value = value;

    theInput.parentNode.parentNode.parentNode.parentNode.style.display = "none";

  } else {

    setSelectedOption(theSelect, value);

    theSelect.parentNode.parentNode.parentNode.style.display = "none";

  }

}

Auch hier nochmal der gesamte JavaScript-Code für die NewForm zum bequemen Kopieren und Einfügen. Nicht vergessen, den Namen des Nachschlagefelds (hier "Parent") an die eigene Umgebung anzupassen! 

<script type="text/javascript">

_spBodyOnLoadFunctionNames.push("setLookupFromQS");

function setLookupFromQS() {

  var lookupId = getQueryStringParameter("LookupId");

  setLookupField("Parent", lookupId);

}

function setLookupField(fieldName, value) {

  var theSelect = getTagFromIdentifierAndTitle("select", "", fieldName),

      theInput;

  if (theSelect == null) {

    theInput = getTagFromIdentifierAndTitle("input", "", fieldName);

    document.getElementById(theInput.optHid).value = value;

    theInput.parentNode.parentNode.parentNode.parentNode.style.display = "none";

  } else {

    setSelectedOption(theSelect, value);

    theSelect.parentNode.parentNode.parentNode.style.display = "none";

 }

}

function getTagFromIdentifierAndTitle(tagName, identifier, title) {

  var idLength = identifier.length,

      tags = document.getElementsByTagName(tagName),

      i,

      tagID;

  for (i = 0; i < tags.length; i++) {

    tagID = tags[i].id;

    if (tags[i].title == title && (identifier == "" || tagID.indexOf(identifier) == tagID.length – idLength)) {

      return tags[i];

    }

  }

  return null;

}

function setSelectedOption(select, value) {

  var opts = select.options,

      optLength = opts.length,

      i;

  for (i = 0; i < optLength; i++) {

    if (opts[i].value == value) {

      select.selectedIndex = i;

      return true;

    }

  }

  return false;

}

function getQueryStringParameter(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 "";

}

</script>

5 Gedanken zu “Link zum Erstellen eines Detaildatensatzes

  1. Hallo Schmakus,
    entschuldige bitte die verspätete Antwort, aber irgendwie sind mir die Benachrichtigungen über neue Kommentare verlorengegangen.
    Du kannst das Problem, wie immer in SharePoint, mit einem zusätzlichen Source-Parameter in der URL beheben. Wenn es den Parameter gibt, leitet SharePoint immer dorthin um.
    Viele Grüße
    Andi

    Gefällt mir

  2. Hey Andi,

    safed my ass! Danke für die Klasse Anleitung!

    Ich hoffe ich bekomme das ganze jetzt noch über einen einzigen Dialog gestartet – dann kann ich entspannt in den Weihnachtsurlaub starten!

    Danke!

    Niclas

    Gefällt mir

  3. Hi, vielen Dank für die tolle Anleitung. Aber bei mir kann ich keine „Verwandte Liste“ einfügen, da diese Knöpfe immer „geisterhaft“ bleiben.

    Kannst du/sie helfen?

    Vielen Dank

    sL

    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