Erfassen Sie Ihre Fortschritte
Trailhead-Startseite
Trailhead-Startseite

Eingeben von Daten mithilfe von Formularen

Lernziele

Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
  • Erstellen eines Formulars zum Anzeigen aktueller Werte und Akzeptieren neuer Benutzereingaben
  • Lesen von Werten aus Formularelementen
  • Überprüfen von Benutzereingaben und Anzeigen von Fehlermeldungen bei ungültigen Eingaben
  • Verlagern von Code aus dem Controller einer Komponente in ihre Hilfsfunktion

Eingeben von Daten mithilfe von Formularen

Mit dieser Einheit sind wir fertig mit Komponenten im helloWhatever-Stil. Ab hier arbeiten wir an der Erstellung und Zusammensetzung der Minianwendung zur Spesenabrechnung, die wir in einer Vorschau kennengelernt haben. Wir verbringen die meiste Zeit in dieser Einheit mit dem Erstellen und Verstehen des Formulars, mit dessen Hilfe Sie neue Spesen erstellen können.

Der Anwendungscontainer expenses

Doch bevor wir damit anfangen, wollen wir auch das Erstellen einfacher (und wirklich hässlicher) Komponenten hinter uns lassen. Das erste, was wir dann tun, ist, das Hinzuziehen des Salesforce Lightning Design System (SLDS), das wir in unserer Anwendung "aktivieren". Die Weise, in der wir dies tun, ermöglicht uns, etwas mehr über Anwendungscontainer zu sprechen.

Hinweis

Hinweis

SDLS selbst wird weder in dieser Einheit noch im restlichen Modul weiter erläutert. Wir konzentrieren uns hier auf das Hinzufügen von SDLS zu einer Anwendung. In unserem Beispielcode verwenden wir anschließend die SDLS-Klassen, ohne diese detailliert zu erläutern. Unter "Ressourcen" finden Sie viele verschiedene Möglichkeiten, mehr über SLDS zu erfahren.

Wenn Ihre Komponenten in Lightning Experience oder der Salesforce-Anwendung ausgeführt werden, steht ihnen SLDS automatisch zur Verfügung. Dies bezeichnen wir mitunter als Ausführung im Container one.app. Diese integrierte Version entspricht derjenigen, die von vielen Lightning-Standardkomponenten verwendet wird. SLDS ist jedoch nicht standardmäßig in einer eigenständigen Anwendung oder bei Verwenden Ihrer Komponenten in Lightning Out oder Lightning Components for Visualforce verfügbar. Es gibt verschiedene Anwendungscontainer, die verschiedene Dienste und Ressourcen bereitstellen. Wir möchten unsere Spesenabrechnungsanwendung so erstellen, dass sie in allen diesen Kontexten funktioniert und ansprechend aussieht. Das ist zum Glück nicht besonders schwierig.

Zunächst fügen wir unserer Containeranwendung SLDS hinzu. Dann können wir in der "realen" Spesenabrechnungsanwendung (d. h. der Komponente auf oberster Ebene und allen ihren untergeordneten Komponenten) SLDS-Tools- und Techniken nutzen, ohne uns darum zu kümmern, woher die SLDS-Ressourcen (Stylesheets, Symbole usw.) stammen. Das heißt, dass unsere Containeranwendung Ressourcen innerhalb ihres Kontexts so einrichtet, dass alle Anwendungen, die in diesem Container ausgeführt werden, über die benötigten Ressourcen verfügen.

Lassen Sie uns diese wortreichen Konzepte mit etwas Code aufhellen. Erstellen Sie die neue Lightning-Anwendung expensesApp.app mit dem folgenden Markup.

<aura:application extends="force:slds">
        <!-- This component is the real "app" -->
        <!-- c:expenses/ -->
</aura:application>

Im Einzelnen geschieht Folgendes. Das Attribut extends="force:slds" aktiviert SLDS in dieser Anwendung durch das Einbeziehen der Lightning Design System-Stile, die auch von Lightning Experience und der Salesforce-Anwendung verwendet werden. Beachten Sie jedoch, dass diese Containeranwendung nur ein Wrapper bzw. eine Shell ist. Die eigentliche Anwendung ist die Komponente expenses, die wir noch nicht erstellt haben. (Dies ist der Teil <!-- c:expenses/ -->, der auskommentiert ist, weil wir unsere Anwendung erst speichern können, sobald die Komponente expenses tatsächlich vorhanden ist.)

Über die Wrapper-Anwendung nutzen unsere Komponenten den Mechanismus extends="force:slds" für den Zugriff auf SLDS, wenn sie in dieser Anwendung ausgeführt werden. Wenn sie in Lightning Experience oder der Salesforce-Anwendung ohne Codeänderungen ausgeführt werden, nutzen sie die automatische Einbeziehung von SLDS des jeweiligen Containers.

Das läuft in diesem Fall auf dasselbe hinaus. Doch dieses Konzept der Nutzung der äußeren Containeranwendung zum Einrichten eines Kontexts dergestalt, dass die eigentliche Anwendung sich nicht um Kontextunterschiede kümmern muss, ist nicht auf Stilressourcen begrenzt. Sie können es z. B. zum Bereitstellen von Austauschereignishandlern nutzen, womit wir allerdings etwas vorgreifen. Lassen Sie uns zuerst das Laufen lernen, bevor wir versuchen zu fliegen!

Die Komponente der Anwendung expenses

Im nächsten Schritt erstellen wir die Komponente, die die oberste Ebene unserer Spesenabrechnungsanwendung darstellt. (Auch wenn wir von einer "Anwendung" reden, handelt es sich bloß um eine weitere Lightning-Komponente.) Erstellen Sie in der Entwicklerkonsole eine neue Aura-Komponente namens "expenses" und ersetzen Sie das Standard-Markup durch das Folgende.

<aura:component>
    <!-- PAGE HEADER -->
    <lightning:layout class="slds-page-header slds-page-header_object-home">
        <lightning:layoutItem>
            <lightning:icon iconName="standard:scan_card" alternativeText="My Expenses"/>
        </lightning:layoutItem>
        <lightning:layoutItem padding="horizontal-small">
            <div class="page-section page-header">
                <h1 class="slds-text-heading_label">Expenses</h1>
                <h2 class="slds-text-heading_medium">My Expenses</h2>
            </div>
        </lightning:layoutItem>
    </lightning:layout>
    <!-- / PAGE HEADER -->
    <!-- NEW EXPENSE FORM -->
    <lightning:layout>
        <lightning:layoutItem padding="around-small" size="6">
        <!-- [[ expense form goes here ]] -->
        </lightning:layoutItem>
    </lightning:layout>
    <!-- / NEW EXPENSE FORM -->
</aura:component>

Was wir hier erstellen, ist die Seitenkopfzeile mithilfe des von den Komponenten <lightning:layout> und <lightning:layoutItem> bereitgestellten Rasterlayouts.size="6" erstellt einen <div>-Container mit 50 % der Gesamtbreite (oder der Größe 6 von 12). Wie Sie vielleicht bemerkt haben, ähneln Komponenten im Namespace lightning Komponenten in Lightning Experience und der Salesforce-Anwendung. Über Schaltflächen und Layouts hinaus finden Sie viele weitere nützliche Komponenten in diesem Namespace, die automatisch ohne Probleme mit den SLDS-Formaten zusammenarbeiten.

Hinweis

Hinweis

Ist Ihnen das Tag <lightning:icon> aufgefallen? Mit dieser Komponente können Sie Ihre SLDS-Lieblingssymbole kinderleicht rendern. Vorbei sind die Zeiten, in denen Sie zum Anzeigen von SLDS-Symbolen eine Hilfskomponente erstellen mussten.

Nun können Sie die Kommentierung des Tags <c:expenses/> in der tatsächlichen APP-Ressource aufheben und die Vorschau einer bislang nur leeren Shell öffnen. Sie sollten etwas Ähnliches wie das Folgende sehen.

Standardformular 'My Expenses'

Hier ist noch nicht viel los, doch es ist schon aufregend, dass die SLDS-Formatierung bereits einen Effekt hat. Wie Sie bereits wissen, erläutern wir den Großteil des SLDS-Markups nicht, fügen aber dem Markup Kommentare hinzu. Sie können erkennen, wie wir die Kopfzeile der Anwendung erstellt haben, und so eine Vorstellung erhalten.

Das neue Spesenformular

Bevor wir mit dem Formular beginnen, müssen wir erst einmal etwas gestehen: Was wir gleich tun werden, ist temporär. Erinnern Sie sich an all den Sermon über das Zerlegen Ihrer Anwendung in getrennte, kleinere Komponenten und die anschließende Entwicklung nach oben? Das machen wir hier nicht (jedenfalls noch nicht) und, offen gesagt, mogeln wir ein bisschen.

Doch es handelt sich um eine "sachdienliche" Mogelei, die verhindern soll, dass der Code zu schnell zu kompliziert wird. Wir gehen so vor, damit wir uns immer nur auf eine Lektion konzentrieren können. Und dies ist keine schlechte Vorgehensweise für Ihre eigene Entwicklung. Entwickeln Sie so lange innerhalb einer Komponente, bis diese zu "ausgelastet" ist, um den Code anschließend in kleinere Unterkomponenten umzugestalten und zu zerlegen. Vergessen Sie auf keinen Fall das Umgestalten!

OK,  </preaching>. Ersetzen Sie in der Komponente expenses den Kommentar <!-- [[ expense form goes here ]] --> durch den folgenden Code für das Formular "Add Expense" (Spesen hinzufügen).

    <!-- CREATE NEW EXPENSE -->
    <div aria-labelledby="newexpenseform">
        <!-- BOXED AREA -->
        <fieldset class="slds-box slds-theme_default slds-container_small">
        <legend id="newexpenseform" class="slds-text-heading_small
          slds-p-vertical_medium">
          Add Expense
        </legend>
        <!-- CREATE NEW EXPENSE FORM -->
        <form class="slds-form_stacked">
            <lightning:input aura:id="expenseform" label="Expense Name"
                             name="expensename"
                             value="{!v.newExpense.Name}"
                             required="true"/>
            <lightning:input type="number" aura:id="expenseform" label="Amount"
                             name="expenseamount"
                             min="0.1"
                             formatter="currency"
                             step="0.01"
                             value="{!v.newExpense.Amount__c}"
                             messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
            <lightning:input aura:id="expenseform" label="Client"
                             name="expenseclient"
                             value="{!v.newExpense.Client__c}"
                             placeholder="ABC Co."/>
            <lightning:input type="date" aura:id="expenseform" label="Expense Date"
                             name="expensedate"
                             value="{!v.newExpense.Date__c}"/>
            <lightning:input type="checkbox" aura:id="expenseform" label="Reimbursed?"
                             name="expreimbursed"
                             checked="{!v.newExpense.Reimbursed__c}"/>
            <lightning:button label="Create Expense"
                              class="slds-m-top_medium"
                              variant="brand"
                              onclick="{!c.clickCreate}"/>
        </form>
        <!-- / CREATE NEW EXPENSE FORM -->
      </fieldset>
      <!-- / BOXED AREA -->
    </div>
    <!-- / CREATE NEW EXPENSE -->

Das sieht nach viel Code aus, der auf einmal zu begreifen ist. Das ist es nicht. Wenn Sie das SLDS-Markup und die Klassen entfernen, besteht dieses Formular nur noch aus einer Reihe von Eingabefeldern und einer Schaltfläche für die Übermittlung des Formulars.

Hier das resultierende Formular.

Neues Spesenformular
Hinweis

Hinweis

<lightning:input> ist das "Schweizer Taschenmesser" für Eingabefelder, das sich durch die Güte der SLDS-Formatierung auszeichnet. Verwenden Sie es, wenn Sie mit Varianten der Komponente <ui:input> wie u. a. <ui:inputText> und <ui:inputNumber> arbeiten möchten. Komponenten im Namespace ui weisen nicht die SLDS-Formatierung auf und gelten als veraltet.

Beachten Sie zuerst, dass wir mehrere Instanzen der <lightning:input>-Komponente mit bestimmten Datentypen erstellen. Sie werden nicht überrascht sein, dass Sie type="date" mit einem Datumsfeld verwenden sollten usw. Über diese angegebenen hinaus gibt es noch verschiedene andere Eingabekomponenten, wobei es stets am besten ist, den Komponententyp an den Datentyp anzupassen. Wenn Sie keinen Typ angeben, wird standardmäßig "Text" verwendet. Der Grund mag noch nicht offensichtlich sein, wird es aber werden, wenn Sie diese Anwendung auf einem Smartphone ausprobieren, denn typspezifische Komponenten können Eingabe-Widgets bereitstellen, die optimal an den Formfaktor angepasst sind. Die Datumsauswahl ist beispielsweise je nachdem, wo der Zugriff erfolgt, für die Maus- oder Fingereingabe optimiert.

Beachten Sie als Nächstes, dass für jede Eingabekomponente eine Beschriftung festgelegt ist und dass der Text von label automatisch neben dem Eingabefeld angezeigt wird. Es gibt einige weitere Attribute, die wir zuvor nicht gesehen haben: required, placeholder, type, min und step. Die meisten dieser Attribute sind ihren HTML-Entsprechungen ähnlich. min gibt beispielsweise den Mindestwert für die Eingabe an. Wenn Sie nicht raten können, wofür diese da sind, können Sie sie in der Lightning-Komponentenbibliothek nachschlagen. (Auf dieses täuschende required kommen wir noch einmal zurück.)

Als Nächstes ist das Attribut aura:id für jedes Tag festgelegt. Was hat es damit auf sich? Es legt eine (lokal) eindeutige ID für jedes Tag fest, dem es hinzugefügt wird. Mithilfe dieser ID lesen Sie Werte aus den Formularfeldern. Die Felder in diesem Beispiel haben alle dieselbe ID, weshalb wir auf sie für die Feldvalidierung als Array zugreifen können. Die entsprechende Vorgehensweise sehen wir uns in Kürze an.

Attribute für Salesforce-Objekte (sObjects)

Doch zuerst müssen wir uns das Attribut value anschauen. Für jedes Tag gibt es einen Wert, der auf einen Ausdruck festgelegt ist. Beispiel: {!v.newExpense.Amount__c}. Anhand des Formats des Ausdrucks sollten Sie einige Dinge folgern können.

  • v bedeutet, dass dies eine Eigenschaft des Wertanbieters "view" ist. Das heißt, dass dies ein Attribut für die Komponente ist. (Das wir noch nicht erstellt haben.)
  • Aufgrund der Punktnotation erkennen Sie, dass "newExpense" eine Art strukturierter Datentyp ist. Das bedeutet, dass "newExpense" selbst Eigenschaften hat. Oder...Felder?
  • Anhand von “__c” am Ende der meisten Eigenschaftennamen können Sie erkennen, dass diese benutzerdefinierten Feldern zugeordnet sind, die am wahrscheinlichsten zum benutzerdefinierten Objekt "Expense" gehören.
  • "newExpense" ist also wahrscheinlich ein "Expense"-Objekt!

Cool, das hatten wir noch nicht besprochen! Es folgt die tatsächliche Attributdefinition, die Sie oben der Komponente hinzufügen müssen, und zwar gleich hinter dem öffnenden <aura:component>-Tag.

    <aura:attribute name="newExpense" type="Expense__c"
         default="{ 'sobjectType': 'Expense__c',
                        'Name': '',
                        'Amount__c': 0,
                        'Client__c': '',
                        'Date__c': '',
                        'Reimbursed__c': false }"/>

Was hier passiert, ist eigentlich ziemlich einfach. Das "Name"-Attribut kennen Sie bereits. "type" ist wenig überraschend der API-Name unseres benutzerdefinierten Objekts. So weit, so gut.

Das Attribut "default" ist nicht neu, im Gegensatz zum Format seines Werts. Das sollte nicht allzu schwer zu verstehen sein. Es handelt sich um die JSON-Darstellung eines sObject, die den Typ des Objekts (wiederum den API-Namen) und Werte für alle Felder angibt, die dafür standardmäßig festgelegt werden. In diesem Fall legen wir im Wesentlichen alles auf eine Darstellung eines leeren Werts fest.

Das ist auch schon alles, was Sie über sObjects wissen müssen! Ab hier ermöglicht Ihnen das Lightning Components-Framework das Arbeiten mit newExpense in JavaScript und im Markup, als wäre dies ein Datensatz aus Salesforce, auch wenn wir diesen (noch) nicht aus Salesforce laden!

Verarbeiten der Formularübermittlung in einem Aktionshandler

Nun haben wir also ein Formular. Was passiert derzeit, wenn Sie es ausfüllen und auf die Schaltfläche zum Erstellen einer neuen Aufwendung klicken? Außer wenn Sie vorgeprescht sind und sie bereits erstellt haben, erhalten Sie die Fehlermeldung, dass eine Controlleraktion fehlt. Der Grund ist, dass weder der Controller noch der für die Schaltfläche <lightning:button> angegebene Aktionshandler bislang erstellt wurden.

Klicken Sie in der Entwicklerkonsole auf die Schaltfläche CONTROLLER für die Komponente expenses, um die Controllerressource zu erstellen. Ersetzen Sie dann den Standardcode durch Folgendes:

({
    clickCreate: function(component, event, helper) {
        let validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
            // Displays error messages for invalid fields
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        // If we pass error checking, do some real work
        if(validExpense){
            // Create the new expense
            let newExpense = component.get("v.newExpense");
            console.log("Create expense: " + JSON.stringify(newExpense));
            helper.createExpense(component, newExpense);
        }
    }
})

Das ist jetzt alles neu, weshalb wir es uns sorgfältig ansehen. Lassen Sie uns zunächst anmerken, dass diese Aktionshandlerfunktion grundsätzlich in drei Abschnitte bzw. Schritte unterteilt ist:

  1. Setup
  2. Verarbeiten von Formularwerten
  3. Wenn keine Fehler auftreten, eine Aktion ausführen

Diese Struktur ist Ihnen möglicherweise vertraut, da so in der Regel Benutzereingaben in eine Webanwendung verarbeitet werden. Lassen Sie uns die Schritte betrachten, um ihre Funktionsweise in der Lightning-Komponente zu verstehen.

Für das Setup initialisieren wir lediglich den Status unserer Fehlerüberprüfung. Es handelt sich um eine einfache Kennzeichnung: Ist dies eine gültige Aufwendung? Bei jedem Aufruf des Aktionshandlers clickCreate gehen wir davon aus, dass die Spesendaten in Ordnung sind. Wenn wir ein Problem finden, machen wir den Vorgang ungültig. Hier folgt ein Überblick über die Kennzeichnung validExpense mit einem auf "true" festgelegten Ausgangswert.

  • component.find('expenseform') ruft einen Verweis auf das Array von <lightning:input>-Feldern ab, die validiert werden müssen. Wenn die ID eindeutig ist, gibt der Verweis die Komponente zurück. In diesem Falle ist die ID nicht eindeutig und der Verweis gibt ein Array von Komponenten zurück.
  • Die JavaScript-Methode reduce() verkleinert das Array auf einen einzelnen Wert, der von validSoFar abgefangen wird. Dieser bleibt solange "true", bis ein ungültiges Feld gefunden wird, wodurch validSoFar in "false" geändert wird. Ein ungültiges Feld kann u. a. ein leeres Pflichtfeld oder ein Feld mit einem unter dem Mindestwert liegenden Wert sein.
  • inputCmp.get('v.validity').valid gibt die Gültigkeit des aktuellen Eingabefelds im Array zurück.
  • inputCmp.showHelpMessageIfInvalid() zeigt eine Fehlermeldung für ungültige Felder.<lightning:input> bietet standardmäßige Fehlermeldungen, die von Attributen wie messageWhenRangeUnderflow angepasst werden können, die Sie im Spesenformularbeispiel sehen.

Lassen Sie uns auf einige interessante Details eingehen. In helloMessageInteractive haben wir nicht find() verwendet, um den Beschriftungstext der Schaltfläche zu bestimmen, auf die geklickt wurde. Das war nicht nötig. Wir konnten einen Verweis auf diese Schaltfläche direkt abrufen, indem wir ihn mithilfe von event.getSource() aus dem Parameter event extrahieren. Diesen Luxus gibt es nicht oft. Tatsächlich ist es selten, dass alles, was Sie aus einer Benutzereingabe benötigen, nur aus dem Ereignis stammt.

Wenn also Ihr Controller eine Möglichkeit zum Abrufen einer untergeordneten Komponente benötigt, legen Sie zuerst das Tag aura:id für diese Komponente im Markup fest und rufen Sie dann mithilfe von component.find(theId) einen Verweis auf die Komponente zur Laufzeit ab.

Hinweis

Hinweis

Mit component.find() können Sie nur über den Controller und das Hilfsprogramm auf die Komponente und ihre untergeordneten Komponenten zugreifen. Es braucht keine Magie, um die Komponentenhierarchie zu durchlaufen und Dinge zu lesen oder zu ändern. Wie Sie sich erinnern, sollen Komponenten eigenständig sein oder kommunizieren mit...doch dazu gleich.

Die Validierung mit <lightning:input> nutzt die Leistungsfähigkeit des zugrunde liegenden HTML-Eingabeelements zum Verarbeiten von Formularwerten aus, damit sie dies in den meisten Fällen nicht tun müssen. Müssen Sie eine Telefonnummer validieren? Verwenden Sie type="tel" und definieren Sie das Attribut pattern mithilfe eines regulären Ausdrucks. Müssen Sie einen Prozentwert validieren? Verwenden Sie type="number" mit formatter="percent". Müssen Sie etwas anderes validieren? Lassen Sie <lightning:input> die Arbeit für Sie erledigen.

Richtig interessant wird es erst wieder, wenn eine Überprüfung misslingt. Wenn der Benutzer etwas Ungültiges eingibt, sollten zwei Dinge passieren:

  1. Nicht versuchen, die Aufwendung zu erstellen
  2. Eine hilfreiche Fehlermeldung anzeigen

Für das erste legen wir die Kennzeichnung validExpense auf "false" fest, wenn die Feldgültigkeit, auf die von inputCmp.get('v.validity').valid verwiesen wird, mit "false" ausgewertet wird. Für das zweite nutzen wir die integrierten Validierungsfehler oder bieten benutzerdefinierte Meldungen für die Fehler. Im Spesenformular wird "Complete this field" im Pflichtfeld "Name" angezeigt, wenn das Feld leer ist und Sie versuchen, das Formular zu übermitteln. Doch Sie können auch eine eigene benutzerdefinierte Meldung bereitstellen, indem Sie messageWhenValueMissing="Did you forget me?" eingeben.

Wenn das Feld dagegen die Validierung besteht, wird die Kennzeichnung validExpense mit "true" ausgewertet und es werden keine Fehler angezeigt.

Und damit sind wir beim 3. Schritt der Verarbeitung der Formularübermittlung angekommen: der Erstellung der Aufwendung! Wie Sie sehen können, rufen wir, um uns darauf vorzubereiten, das vollständige newExpense-Objekt aus dem Attribut "component" ab: component.get("v.newExpense"). Dadurch erhalten wir eine einzelne Variable, mit deren Hilfe wir einen neuen Spesendatensatz erstellen können.

Doch bevor wir dorthin gelangen, hier noch eine Frage zum Nachdenken: Warum rufen wir die Formularwerte nicht aus newExpense ab? Was spricht gegen das Abrufen der strukturierten Variable am Anfang des Aktionshandlers und den anschließenden Zugriff auf ihre Eigenschaften in Vergleich mit einer möglicherweise langen Reihe von Aufrufen von find().get()?

Die Antwort ist einfach: Weil wir Verweise auf die einzelnen Felder brauchen, damit wir showHelpMessageIfInvalid() dafür festlegen können. Es empfiehlt sich zudem, die unformatierten Formulardaten zu überprüfen, denn Ihre Überprüfungslogik weiß nicht, welche Arten von Verarbeitung ggf. im newExpense-Objekt erfolgen.

Erstellen der neuen Aufwendung

Erinnern Sie sich noch, dass wir zuvor gesagt haben, dass das Ablegen des Spesenformulars in der Hauptkomponente so etwas wie eine Mogelei sei? Das "etwas wie" wird auch noch nicht im nächsten Abschnitt aufgelöst. Was wir hier vorhaben, ist das unbedingte Vermeiden der Komplexität der tatsächlichen Erstellung des Datensatzes. Das machen wir hier, weil wir uns in der kompletten nächsten Einheit damit beschäftigen. Lassen Sie uns das aktuelle Thema mit etwas Einfachem zum Abschluss bringen, das uns allerdings einige wichtige Konzepte veranschaulicht.

Zunächst wollen wir einen Speicherort für neue Spesen erstellen. Dazu legen wir ein ausschließlich lokales Array für Spesen an. Fügen Sie oben im Markup der Komponente expenses direkt vor dem Attribut newExpense ein neues Attribut expenses hinzu, das ein Array von Spesenobjekten enthalten soll.

    <aura:attribute name="expenses" type="Expense__c[]"/>

Wir müssen dann nur noch das Array expenses aktualisieren. Wie sich herausstellt, ist das einfach (wenigstens in der Mogelpackungsversion unseres Formulars). Außerdem wird noch ein weiteres wichtiges Konzept veranschaulicht.

In unserem Controller haben wir die Aufgabe des tatsächlichen Erstellens der neuen Aufwendung hinter diesem Funktionsaufruf versteckt: helper.createExpense(component, newExpense). In der Softwareentwicklung ist Abstraktion ein anderes Wort für "verstecken". Und wir nutzen etwas, das als Hilfsfunktion bezeichnet wird, um unsere Mogelei weg zu abstrahieren.

Wir haben Hilfsfunktionen zuvor kurz angesprochen und werden in diesem Modul keine erweiterten Details dazu erörtern. Für den Moment gibt es drei Dinge über Hilfsfunktionen zu sagen:

  • Die Hilfsfunktion einer Komponente eignet sich zum Ablegen von Code, der von mehreren Aktionshandlern gemeinsam genutzt werden soll.
  • Die Hilfsfunktion einer Komponente empfiehlt sich besonders zum Auslagern komplexer Verarbeitungsdetails, damit die Logik Ihrer Aktionshandler klar und übersichtlich bleibt.
  • Hilfsfunktionen können eine beliebige Funktionssignatur enthalten. Das heißt, dass sie nicht so eingeschränkt sind wie Aktionshandler im Controller. (Warum ist das so? Weil Sie die Hilfsfunktion direkt in Ihrem Code aufrufen. Im Gegensatz dazu ruft das Framework Aktionshandler über die Laufzeit des Frameworks auf.) Es ist eine Konvention und empfohlene Praxis, bei Hilfsfunktionen die Komponente stets als ersten Parameter anzugeben.

Legen wir also los. Klicken Sie in der Entwicklerkonsole auf die Schaltfläche HELPER für die Komponente expenses, um die dazugehörige Hilfsfunktionsressource zu erstellen. Ersetzen Sie anschließend den Beispielcode durch Folgendes.

({
    createExpense: function(component, expense) {
        let theExpenses = component.get("v.expenses");
        // Copy the expense to a new object
        // THIS IS A DISGUSTING, TEMPORARY HACK
        let newExpense = JSON.parse(JSON.stringify(expense));
        theExpenses.push(newExpense);
        component.set("v.expenses", theExpenses);
    }
})

Ignorieren Sie für den Moment den Teil "THIS IS A DISGUSTING, TEMPORARY HACK". Die anderen drei Codezeilen veranschaulichen ein gängiges Muster, das wir bereits zuvor gesehen haben und das Sie immer wieder befolgen werden: get-process-set (Abrufen-Verarbeiten-Festlegen). Zuerst rufen wir das Array von Spesen aus dem Attribut expenses ab. Anschließend fügen wir ihm den neuen "Spesendatensatz" hinzu. Danach aktualisieren wir (mit set) das Attribut expenses mit dem geänderten Array.

Der Verweis ist nicht die Auflistung

Hier ist neu, dass wir erstmals eine Auflistung bzw. ein Array aktualisieren. Als erfahrener Programmierer fragen Sie sich wahrscheinlich: "Warum brauche ich set() hier?"

component.get("v.expenses") ruft also einen Verweis auf das Array ab, das im Attribut component gespeichert ist.component.set("v.expenses", theExpenses) legt lediglich das Attribut component auf denselben Verweis fest. Sicherlich wurde zwischendrin der Inhalt des Arrays hinzugefügt, doch der Container ist derselbe: Der Verweis auf das Array hat sich eigentlich nicht geändert! Warum soll er dann aktualisiert werden?

Wenn Sie Probleme mit dem Verständnis haben, fügen Sie zwei Protokollierungsanweisungen vor und hinter den kritischen Anweisungen hinzu und geben Sie den Inhalt von theExpenses in der Konsole aus.

console.log("Expenses before 'create': " + JSON.stringify(theExpenses));
theExpenses.push(newExpense);
component.set("v.expenses", theExpenses);
console.log("Expenses after 'create': " + JSON.stringify(theExpenses));

Führen Sie die neu geladene Anwendung aus, fügen Sie mindestens zwei Spesen hinzu und sehen Sie sich die Struktur von theExpenses an. Kommentieren Sie jetzt die Zeile component.set() aus und wiederholen Sie den Vorgang.

component.set() wirkt sich überhaupt nicht auf theExpenses aus! Aber! Aber! Aber? Was macht "component.set()" hier eigentlich?!?

Sie haben unbedingt das Recht, diese Frage zu stellen. Die Antwort lautet: Magie!

component.set() aktualisiert hier nicht den Wert des Attributs expenses. Es löst die Benachrichtigung aus, dass sich das Attribut expenses geändert hat.

Die Folge ist, dass an allen Stellen in Ihrer Anwendung, an denen Sie auf das Attribut expenses in einem Ausdruck verwiesen haben, der Wert dieses Ausdrucks aktualisiert wird. Diese Aktualisierung wird überall dorthin weitergegeben, wo da Attribut expenses genutzt wurde. Alle diese Stellen werden mit dem neuen Inhalt erneut gerendert. All dies erfolgt im Hintergrund mithilfe des Lightning Components-Frameworks im Rahmen der automatischen Verknüpfung, wenn Sie {!v.expenses} verwenden. Das nennen wir einfach Magie!

Wir fassen zusammen: Wenn es sich um reines JavaScript handeln würde, wäre component.set() nicht erforderlich. Doch um zugrunde liegende Effekte auszulösen, die im Programmiermodell von Aura-Komponenten integriert sind, brauchen Sie dieses Element. Wenn Sie jemals Controller- oder Hilfsfunktionscode schreiben, diesen testen und nichts passiert, vergewissern Sie sich, dass das erforderliche Element component.set() festgelegt ist.

Der "DISGUSTING HACK" bietet eine Umgehung eines ähnlichen Problems mit Verweisen. Um das Problem anzuzeigen, ändern Sie die Zeile, indem Sie die beiden JSON-Aufrufe entfernen und dann die Anwendung testen. Sie werden schnell genug feststellen, was das Problem ist. Wir beseitigen es in der nächsten Einheit, weshalb hier keine weitere Erläuterung erfolgt.

Anzeigen der Liste der Spesen

Nach all dem Gerede über das "magische" Aktualisieren aller Stellen, an denen {!v.expenses} verwendet wird, raten Sie mal. Es wird nirgendwo verwendet, also noch nicht. Das beheben wir jetzt.

Erstellen Sie in der Entwicklerkonsole eine neue Aura-Komponente namens expenseItem und ersetzen Sie das Standard-Markup durch das Folgende. Wenn Sie expenseItem bereits erstellt haben, aktualisieren Sie lediglich das Markup. Sie haben zuvor die Ausdrücke gesehen, die auf Felder im Spesendatensatz zugreifen. Diese Version enthält SLDS-Markup, um es ansprechender zu gestalten.
<aura:component>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:attribute name="formatdate" type="Date"/>
    <aura:attribute name="expense" type="Expense__c"/>
    <lightning:card title="{!v.expense.Name}" iconName="standard:scan_card"
                    class="{!v.expense.Reimbursed__c ?
                           'slds-theme_success' : ''}">
        <aura:set attribute="footer">
            <p>Date: <lightning:formattedDateTime value="{!v.formatdate}"/></p>
            <p class="slds-text-title"><lightning:relativeDateTime value="{!v.formatdate}"/></p>
        </aura:set>
        <p class="slds-text-heading_medium slds-p-horizontal_small">
           Amount: <lightning:formattedNumber value="{!v.expense.Amount__c}" style="currency"/>
        </p>
        <p class="slds-p-horizontal_small">
            Client: {!v.expense.Client__c}
        </p>
        <p>
            <lightning:input type="toggle"
                             label="Reimbursed?"
                             name="reimbursed"
                             class="slds-p-around_small"
                             checked="{!v.expense.Reimbursed__c}"
                             messageToggleActive="Yes"
                             messageToggleInactive="No"
                             onchange="{!c.clickReimbursed}"/>
        </p>
    </lightning:card>
</aura:component>
<lightning:card> weist ein SLDS-Design zu, wenn das Feld Reimbursed? (Erstattet) für den Spesenbelegposten aktiviert ist: {!v.expense.Reimbursed__c ? 'slds-theme_success' : ''}. Dieser Ausdruck gibt Ihnen auf der Benutzeroberfläche die Kontrolle über das Erscheinungsbild der erstatteten Spesenbelegposten. Ohne weitere Anpassungen werden die Spesenbelegposten im Stil der Komponente expensesList mit den Spesenbelegposten angezeigt. Um die Komponente expenseItem anzupassen, klicken Sie auf STYLE und fügen Sie Folgendes hinzu.
.THIS.slds-card.slds-theme_success {
    background-color: rgb(75, 202, 129);
}

Erstellen Sie als Nächstes mit dem folgenden Code das clientseitige Steuerfeld expenseItemController.js. Hier konvertieren wir das Datum, das vom Server später zurückgegeben wird, in ein JavaScript-Objekt des Typs "Date", damit es von <lightning:formattedDateTime> und <lightning:relativeDateTime> ordnungsgemäß angezeigt wird. Diese Konvertierung erfolgt während der Initialisierung der Komponente, die vom Tag <aura:handler> erfasst wird. Dies ist eine sinnvolle Möglichkeit der Verarbeitung des Initialisierungsereignisses, die wir später erneut nutzen, wenn wir Daten aus Salesforce laden.

({
    doInit : function(component, event, helper) {
        let mydate = component.get("v.expense.Date__c");
        if(mydate){
            component.set("v.formatdate", new Date(mydate));
        }
    },
})
Erstellen Sie in der Entwicklerkonsole eine Aura-Komponente namens expensesList und ersetzen Sie das Standard-Markup durch das Folgende.
<aura:component>
    <aura:attribute name="expenses" type="Expense__c[]"/>
    <lightning:card title="Expenses">
        <p class="slds-p-horizontal_small">
            <aura:iteration items="{!v.expenses}" var="expense">
                <c:expenseItem expense="{!expense}"/>
            </aura:iteration>
        </p>
    </lightning:card>
</aura:component>

Hier gibt es für Sie nicht viel Neues. Dies ist eine Komponente, die eine Liste mit Spesen anzeigt. Sie hat das eine Attribut expenses, bei dem es sich um ein Array von expense-Objekten (Expense__c) handelt. Und sie verwendet das Tag <aura:iteration> zum Erstellen eines <c:expenseItem> für jedes dieser Spesenobjekte. Ihr Hauptzweck ist das Anzeigen einer Liste von Spesen. So weit, so gut.

Fügen Sie nun die Komponente expensesList am Ende der Komponente expenses hinzu. Fügen Sie sie direkt vor dem schließenden </aura:component>-Tag in expenses.cmp hinzu.

<c:expensesList expenses="{!v.expenses}"/>

Wenn Sie die Anwendung neu laden, wird unter dem Formular der Abschnitt "Expenses" angezeigt. (Visuell stimmt hier noch nicht alles, doch für den Moment ist es gut genug.)

Was haben wir gerade gemacht? Wir haben die Komponente expensesList hinzugefügt und das Hauptattribut expenses an diese übergeben. Nun ist also die Instanz von expenses, über die expensesList verfügt, identisch mit der Instanz von expenses, die expenses hat. Es handelt sich um Verweise auf dasselbe Array von Datensätzen. Dank der Magie von Lightning Components bemerkt die Komponente expensesList, wenn das Hauptarray von expenses aktualisiert wird, und rendert ihre Liste erneut. Testen Sie es.

Puh! Das war eine lange Einheit, weshalb wir uns eine längere Pause verdient haben. Bitte stehen Sie auf und bewegen Sie sich ein paar Minuten.

Wenn Sie zurückkommen, zeigen wir Ihnen, wie Sie Ihre neuen Spesen tatsächlich speichern können.