Seitengröße
Vollständiger Name | E-Mail | Telefon | Alter | Konto | |
|---|---|---|---|---|---|
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
|
Auf / SubGrid gesetzt und ein Schalter AllowEdit="true" MainGrid mit der Beschriftung Editable erscheint im Zahnradmenü-Overflow. Das Einschalten schaltet jede deklarierte Spalte, deren Zellrenderer nicht überschrieben wird, in einen Inline-Editor um – der automatisch aus den Dataverse-Metadaten der Spalte gesendet wird –, während ein GridTemplateColumn's EditChildContent den Lesemodus ChildContentübernimmt. Bearbeitungen sammeln sich als ausstehende Zeilenänderungen im transaktionalen Puffer des Grids auf und landen nur in Dataverse, wenn der Benutzer speichert.
A MainGrid über dem contact Tisch mit AllowEdit="true". Klicken Sie auf das Zahnrad-Symbol in der Symbolleiste und aktivieren Sie Bearbeitend. Die Spalte Full Name tauscht ihre Initialen-Avatar-Lesevorlage gegen einen benutzerdefinierten Einzelfeld-Editor, der die Benutzereingabe wieder in firstname + lastnameaufteilt. Die bloßen Spalten E-Mail, Telefon, Alter und Konto leuchten jeweils auf, wobei der Editor ihren Metadatentyp – TextEdit für die Zeichenketten, NumberEdit für die Ganzzahl, LookupEdit für die Kundenreferenz – abstimmt, ohne jede Spalte zu verdrahten.
Seitengröße
Vollständiger Name | E-Mail | Telefon | Alter | Konto | |
|---|---|---|---|---|---|
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
|
Auf dem Raster gesetzt AllowEdit="true" . Das Bearbeiten erfolgt pro Raster – der Schalter erscheint nur, wenn der Parameter gesetzt ist, und der Schalter selbst ist nur sichtbar, wenn mindestens eine Spalte bearbeitbar ist. Während der Schalter ausgeschaltet ist, verhält sich das Raster wie eine Nur-Lese-Ansicht; Während sie aktiv ist, werden die Zellen ihre Inline-Editoren gerendert und nicht gespeicherte Änderungen werden pro Zeile verfolgt.
<MainGrid tableName="contact" allowEdit>
<GridColumns>
<GridColumn columnName="firstname" />
<GridColumn columnName="lastname" />
<GridColumn columnName="emailaddress1" />
</GridColumns>
</MainGrid><MainGrid TableName="contact" AllowEdit="true">
<GridColumns>
<GridColumn ColumnName="firstname" />
<GridColumn ColumnName="lastname" />
<GridColumn ColumnName="emailaddress1" />
</GridColumns>
</MainGrid>Warum ein Schalter?
Inline-Edit ändert das Rendering der Zellen für jede sichtbare Zeile, was eine andere Leseaffordanz als ein statisches Raster darstellt. Der Schalter erlaubt es den Nutzern, die Bearbeitungsmöglichkeit zu wählen, wenn sie sie brauchen (einen Wert eingeben, Tabben über Zellen hinweg usw.) und behält die sauberere Lesemöglichkeit für den Rest der Zeit. Das Raster merkt sich den Umschaltstatus der Sitzung.
Bare-Deklarationen <GridColumn ColumnName="..." /> ohne ChildContent automatisches Dispatchen des richtigen Inline-Editors, während das Raster im Bearbeitungsmodus ist. Das Framework liest die Metadaten der Spalte und wählt die Editor-Unterklasse aus:
String, Memo, EmailAddress, Phone, — Url TextEdit (mit formatgerechter Validierung).Integer, Decimal, Double, BigInt — NumberEdit (ortsbewusstes Parsen, Min/Max aus Metadaten).Boolean — BoolEdit (dargestellt als Schalter, zentrierte Ausrichtung).Picklist — ChoiceEdit (option-set-gesteuerte Dropdown-Menü, lokalisierte Labels).MultiSelectPicklist — MultiSelectChoiceEdit.Lookup, Customer, Owner — LookupEdit (typeahead-gesteuert, respektiert Erlaubnislisten der Zieltabelle).DateTime, DateOnly — DateTimeEdit.Money— MoneyEdit (Währungssymbolpräfix aus ).transactioncurrencyidFile, Image — FileEdit / ImageEdit (Drag-Drop-Upload).Bereitstellen ChildContent Sie ein GridColumn , um sowohl die Lesemodusanzeige ALS auch den Bearbeitungsmodus-Editor für diese Spalte zu übernehmen. Sobald ChildContent gesetzt ist, hört das Framework auf, den metadatengesteuerten Editor automatisch zu senden – rendert den gewünschten Editor innerhalb der Vorlage und sperrt dessen Sichtbarkeit vom Grid-Flag Editable ab. Standard-Editoren (TextEdit, NumberEdit, ) ColumnEditwurden in die Vorlage automatisch verdrahtet, um über den kaskadierenden Zeilenkontext Dirty Tracking und Validierung durchzuführen.
{/* Benutzerdefinierte Anzeigezelle PLUS benutzerdefinierter Editor. React teilt die beiden auf
halbiert in cellRenderer (nur lesbar) und editRenderer (Bearbeitungsmodus) —
verwenden <GridTemplateColumn> , damit beide Renderer für die</GridTemplateColumn>
Gleiche logische Spalte. Die heruntergelassenen <TextEdit> Selbstregistrierungen für</TextEdit>
Dirty-Tracking und Validierung über den kaskadierenden Zeilenkontext. */}
<GridTemplateColumn
displayName="E-Mail"
dependsOn={['emailaddress1']}
sortBy="emailaddress1"
cellRenderer={({ record }) => {
const email = (record.properties?.emailaddress1 as { value?: string })?.value;
return <span>{email}</span>;
}}
editRenderer={() => (
<TextEdit columnName="emailaddress1" displayLabelWhenAvailable={false} />
)}
/><!-- Benutzerdefinierte Anzeigezelle PLUS benutzerdefinierter Editor. Sobald ChildContent ist
Bereitgestellt wird das Framework nicht mehr automatisch eine Inline gesendet
Redakteur für diese Kolumne, also rendere selbst eine, wenn du möchtest
Die Zelle ist bearbeitbar. Der eingesteckte Editor registriert sich selbst für
Dirty-Tracking und Validierung über den kaskadierenden Zeilenkontext. -->
<GridColumn ColumnName="emailaddress1">
<ChildContent>
@{
var email = context.PrimaryRecord
.GetValueOrDefault<StringValue>("emailaddress1")?.Value;
}
@if (this.Editable)
{
<TextEdit ColumnName="emailaddress1"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
}
else
{
<span>@email</span>
}
</ChildContent>
</GridColumn>Jede Reihe behält ihre eigene EditContextValidator. Standard-Editoren registrieren ihn am Mount und weisen ihre Fehler in der Reihe auf (rote Unterstreichung + Tooltip auf der Zelle). Die Speichertaste des Rasters ist deaktiviert, während jede Zeile einen ungelösten Validierungsfehler zeigt. Validierungsregeln stammen aus den Spaltenmetadaten:
RequiredLevel — ist die Spalte erforderlich, und ein leerer Wert blockiert das Speichern.Format auf String-Spalten – E-Mail, Telefon, URL-Formate erzwingen die Form oben auf dem Typ.MinValue / MaxValue in numerischen Spalten — Werte außerhalb des Bereichs werden vor dem Speichern markiert.EditContextValidator API für feldübergreifende Invarianten registrieren (z. B. "Kontakt muss entweder E-Mail oder Telefon haben").Validierung bei benutzerdefinierten Widgets
Benutzerdefinierte Eingaben, die innerhalb
ChildContentgerendert werden oderEditChildContentnicht erweitertBaseEditwerden (z. B. ein RAWFluentTextFieldoder ein Drittanbieter-Widget), werden nicht automatisch beim Validator der Zeile registriert. Wenn du sie validieren möchtest, verzichte entweder auf einen Standardeditor statt auf das rohe Widget oder führe die Validierung manuell im Callback des WidgetsValueChangeddurch, bevor du aufgerufeneditCtx.SetValuewirst.
Bearbeitungen sammeln sich als ausstehende Updates in der Zeile an – modifizierte Zellen erhalten einen kleinen "schmutzig"-Indikator, gelöschte Zeilen werden durchgestrichen, und ausstehende Erstellungszeilen werden als neu angezeigt. Die Werkzeugleiste des Rasters zeigt Speichern und Abbrechen-Buttons auf, wenn ausstehende Änderungen vorliegen: Save commit den Batch in einem transaktionalen Durchgang; Cancel verwirft den Puffer und setzt jede Zeile auf ihren Serverzustand zurück. Koppele mit NewRecordGridButton und DeleteRecordGridButton im Fragment Buttons , um Zeilenerstellung und -löschung zur selben Bearbeitungstransaktion hinzuzufügen.
<MainGrid tableName="contact" allowEdit>
<GridColumns>
<GridColumn columnName="firstname" />
<GridColumn columnName="lastname" />
<GridColumn columnName="emailaddress1" />
</GridColumns>
<GridButtons>
<NewRecordGridButton>
<NewContactForm />
</NewRecordGridButton>
<DeleteRecordGridButton />
</GridButtons>
</MainGrid><MainGrid TableName="contact" AllowEdit="true">
<GridColumns>
<GridColumn ColumnName="firstname" />
<GridColumn ColumnName="lastname" />
<GridColumn ColumnName="emailaddress1" />
</GridColumns>
<Buttons>
<NewRecordGridButton TForm="NewContactForm" />
<DeleteRecordGridButton />
</Buttons>
</MainGrid>Koppele den Lesemodus ChildContent von 'GridTemplateColumns mit an, EditChildContent um eine synthetische/zusammengesetzte Spalte editierbar zu machen. Die Bearbeitungsvorlage erhält einen GridTemplateColumnEditContext — denselben Zeilenkontext wie die Lesevorlage (editCtx.Row.PrimaryRecord, der optionale editCtx.Row.ParentRecord in einem SubGrid, editCtx.DependsOnMetadata) plus einen imperativen editCtx.SetValue(columnName, value) Helfer.
EditChildContent löst nur aus, wenn das Raster bearbeitbar ist (AllowEdit="true" UND der Wechselbare Schalter aktiviert ist) und die Zeile nicht auf 'Pending-Delete' steht. Vorlagenspalten, die sie weglassen EditChildContent , bleiben auch im Bearbeitungsmodus nur lesbar – nützlich für Aktionsspalten, die immer dieselben Buttons darstellen sollten.
Der häufigste Fall: Die Zelle besteht aus mehreren gebundenen Spalten, und du möchtest dem Benutzer erlauben, jede einzelne zu bearbeiten. Fügen Sie die Matching <TextEdit> / <NumberEdit> / Editoren <ColumnEdit> hinein EditChildContent – das Framework kaskadierend den primären Datensatz und das Matching EditContextValidator der Reihe in diesen Scope, sodass die Editoren sich selbst registrieren, um Dirty Tracking und Validierung zu überprüfen. Keine zusätzliche Verkabelung erforderlich.
<GridTemplateColumn
displayName="Vollständiger Name"
dependsOn={['firstname', 'lastname']}
sortBy="lastname"
cellRenderer={({ record }) => {
const first = (record.properties?.firstname as { value?: string })?.value ?? '';
const last = (record.properties?.lastname as { value?: string })?.value ?? '';
return <strong>{first} {last}</strong>;
}}
// Drop-in-Editoren – TextEdit verdrahtet automatisch zu Dirty-Tracking
// und Validierung über den kaskadierenden Zeilenkontext.
editRenderer={() => (
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<TextEdit columnName="firstname" displayLabelWhenAvailable={false} />
<TextEdit columnName="lastname" displayLabelWhenAvailable={false} />
</div>
)}
/><GridTemplateColumn Title="Vollständiger Name"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<ChildContent Context="ctx">
<strong>
@(ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value)
@(ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value)
</strong>
</ChildContent>
<!-- Drop-in-Editoren – TextEdit verdrahtet automatisch zu Dirty-Tracking
und Validierung über den kaskadierenden Zeilenkontext. -->
<EditChildContent Context="editCtx">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="6"
VerticalAlignment="VerticalAlignment.Center">
<TextEdit ColumnName="firstname"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
<TextEdit ColumnName="lastname"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
</FluentStack>
</EditChildContent>
</GridTemplateColumn>Wenn die Editor-Oberfläche nicht 1:1 einer Dataverse-Spalte zugeordnet ist – also einem einzigen "First Last"-Textfeld, das in zwei Spalten geteilt wird, einem Schieberegler, der an mehrere Prozentfelder gebunden ist, einem benutzerdefinierten Date-Range-Picker, der Start + Ende schreibt – rendere du für jede betroffene Spalte deine eigene Eingabe und den Aufruf editCtx.SetValue(columnName, value) . Das Framework leitet den Schreib an die richtige AliasedTableRecord Stelle und startet ValueChanged , sodass Dirty-Tracking die Änderung erkennt.
<GridTemplateColumn
displayName="Vollständiger Name"
dependsOn={['firstname', 'lastname']}
sortBy="lastname"
cellRenderer={({ record }) => {
const first = (record.properties?.firstname as { value?: string })?.value ?? '';
const last = (record.properties?.lastname as { value?: string })?.value ?? '';
return <strong>{first} {last}</strong>;
}}
editRenderer={({ record, setValue }) => {
const first = (record.properties?.firstname as { value?: string })?.value ?? '';
const last = (record.properties?.lastname as { value?: string })?.value ?? '';
const combined = [first, last].filter(Boolean).join(' ');
// Benutzerdefiniertes Widget – aufgeteilt nach Platz; Verbraucher nehmen Kritik
// Validierung da <Input> registriert sich nicht selbst.
return (
<Input
value={combined}
onChange={(_, data) => {
const next = data.value ?? '';
const idx = next.indexOf(' ');
const head = idx === -1 ? next : next.slice(0, idx);
const tail = idx === -1 ? '' : next.slice(idx + 1);
setValue('firstname', { $type: 'StringValue', value: head });
setValue('lastname', { $type: 'StringValue', value: tail || null });
}}
/>
);
}}
/><GridTemplateColumn Title="Vollständiger Name"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<ChildContent Context="ctx">
@{
var first = ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value;
var last = ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value;
}
<strong>@first @last</strong>
</ChildContent>
<EditChildContent Context="editCtx">
@{
var combined =
(editCtx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value ?? "")
+ " "
+ (editCtx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value ?? "");
}
<!-- Benutzerdefiniertes Widget – aufgeteilt nach Platz; Verbraucher nehmen Kritik
Validierung, da FluentTextField sich nicht selbst registriert. -->
<FluentTextField Value="@combined.Trim()" ValueChanged="@(next =>
{
var parts = next.Split(' ', 2);
editCtx.SetValue("firstname", new StringValue { Value = parts[0] });
editCtx.SetValue("lastname", new StringValue { Value = parts.Length > 1 ? parts[1] : null });
})" />
</EditChildContent>
</GridTemplateColumn>Validierung auf dem imperativen Weg
Standard-Editoren wurden automatisch in
EditChildContentdie Register der ZeilenEditContextValidatoreingefügt und nehmen am Validate-before-Save-Gate des Frameworks teil. Der PfadeditCtx.SetValueumgeht diese Pipeline – Werte, die nicht von einem registrierten Editor stammen, werden nicht validiert. Wenn dein benutzerdefiniertes Widget Constraints erzwingen muss, führe sie imValueChangedCallback aus, bevor du aufrufstSetValue, oder greife auf das Drop-in-Editor-Muster zurück.