Tamaño de página
Nombre completo | Correo electrónico | Teléfono | Edad | Cuenta | |
|---|---|---|---|---|---|
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
|
Al configurar AllowEdit="true" en MainGrid / SubGrid aparece un interruptor etiquetado como Editable en el desbordamiento del menú de engranajes. Al activarlo, cada columna declarada cuyo renderizador de celda no se sobrescribe a un editor en línea — despachado automáticamente desde los metadatos Dataverse de la columna — mientras que un GridTemplateColumn's EditChildContent toma el relevo de su modo ChildContentde lectura. Las ediciones se acumulan como cambios pendientes en filas en el búfer transaccional de la cuadrícula y solo aparecen en Dataverse cuando el usuario guarda.
Un MainGrid sobre la contact tabla con AllowEdit="true". Haz clic en el icono de engranaje en la barra de herramientas y activa Editable . La columna Nombre Completo cambia su plantilla de lectura iniciales y avatar por un editor personalizado de un solo campo que divide la entrada del usuario en firstname + lastname. Las columnas básicas de Correo electrónico, Teléfono, Edad y Cuenta se iluminan con el editor que coincide con su tipo de metadatos — TextEdit para las cadenas, NumberEdit para el entero, LookupEdit para la referencia del cliente — sin necesidad de cableado por columna.
Tamaño de página
Nombre completo | Correo electrónico | Teléfono | Edad | Cuenta | |
|---|---|---|---|---|---|
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
| |||||
|
|
Configurado AllowEdit="true" en la cuadrícula. La edición es opt-in por cuadrícula: el interruptor solo aparece cuando el parámetro está activado, y el interruptor solo es visible cuando al menos una columna es editable. Mientras el interruptor está desactivado, la cuadrícula se comporta como una vista de solo lectura; Mientras está activado, las celdas renderizan sus editores en línea y los cambios no guardados se registran por fila.
<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>¿Por qué un interruptor de activación?
La edición en línea cambia el renderizado de las celdas para cada fila visible, lo que tiene una accesibilidad de lectura diferente a una cuadrícula estática. El interruptor permite a los usuarios optar por la opción de edición cuando la necesiten (escribiendo un valor, desplazando a través de celdas, etc.) y mantener la opción de lectura limpia el resto del tiempo. La cuadrícula recuerda el estado de alternancia para la sesión.
<GridColumn ColumnName="..." /> Las declaraciones sin ChildContent despacho automático del editor en línea derecho mientras la cuadrícula está en modo edición. El framework lee los metadatos de la columna y elige la subclase editor:
String, Memo, EmailAddress, Phone, Url — TextEdit (con validación apropiada al formato).Integer, Decimal, Double, BigInt — NumberEdit (análisis sintáctico según la localidad, mínimo/máximo a partir de los metadatos).Boolean — BoolEdit (renderizado como un interruptor, alineación centrada).Picklist — ChoiceEdit (desplegable basado en el conjunto de opciones, etiquetas localizadas).MultiSelectPicklist — MultiSelectChoiceEdit.Lookup, Customer, Owner — LookupEdit (guiado por la cabeza de tipos, respeta las listas de permisos de la tabla objetivo).DateTime, DateOnly — DateTimeEdit.Money — MoneyEdit (prefijo de símbolo de moneda de transactioncurrencyid).File, Image — / FileEdit ImageEdit (arrastrar y soltar subir).Suministra ChildContent en un GridColumn para asumir tanto la pantalla de modo lectura COMO el editor de modo de edición para esa columna. Una vez ChildContent configurado, el framework deja de despachar automáticamente el editor basado en metadatos: renderiza el editor que quieras dentro de la plantilla y bloquea su visibilidad desde la bandera de Editable la red. Los editores estándar (TextEdit, NumberEdit, ColumnEdit) se insertaban en la plantilla se conectaban automáticamente al dirty-tracking y validación mediante el contexto de filas en cascada.
{/* Celda de pantalla personalizada MÁS editor personalizado. React separa a los dos
se divide a la mitad en cellRenderer (solo lectura) y editRenderer (modo edición) —
<GridTemplateColumn> úsate para que ambos renderizadores puedan coexistir para el</GridTemplateColumn>
Misma columna lógica. Los <TextEdit> registros propios drop-in para</TextEdit>
seguimiento sucio y validación mediante el contexto de la fila en cascada. */}
<GridTemplateColumn
displayName="Correo electrónico"
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} />
)}
/><!-- Celda de pantalla personalizada MÁS editor personalizado. Una vez que ChildContent es
Suministrado, el marco ya no despacha automáticamente un Inline
editor de esta columna, así que haz uno tú mismo si quieres
la celda editable. El editor drop-in se registra automáticamente para
seguimiento sucio y validación mediante el contexto de la fila en cascada. -->
<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>Cada fila mantiene su propio EditContextValidator. Los editores estándar registran con él en el montaje y muestran sus errores en línea (subrayado rojo + descripción emergente en la celda). El botón de guardar de la cuadrícula está deshabilitado mientras que cualquier fila tiene un error de validación sin resolver. Las reglas de validación provienen de los metadatos de columna:
RequiredLevel — la columna es requerida, y un valor vacío bloquea guardar.Format en columnas de cadena — los formatos de correo electrónico, teléfono y URL imponen la forma encima del tipo.MinValue / MaxValue en columnas numéricas — los valores fuera de rango se marcan antes de guardar.EditContextValidator API para invariantes entre campos (por ejemplo, "el contacto debe tener correo electrónico o teléfono").Validación en widgets personalizados
Las entradas personalizadas renderizadas dentro
ChildContentde las queEditChildContentno se extiendenBaseEdit(por ejemplo, un widget rawFluentTextFieldo de terceros) no se registran automáticamente con el validador de la fila. Si necesitas que se validen, o bien elimina un editor estándar en lugar del widget en bruto, o ejecuta la validación manualmente dentro de la callback delValueChangedwidget antes de llamareditCtx.SetValuea .
Las ediciones se acumulan como actualizaciones pendientes en la fila: las celdas modificadas reciben un pequeño indicador de "sucio", las filas eliminadas se tachan y las filas de creación pendiente aparecen como nuevas. La barra de herramientas de la cuadrícula activa los botones de Guardar y Cancelar siempre que hay cambios pendientes: Guardar hace commit del lote en un solo pase transaccional; Cancelar descarta el búfer y vuelve cada fila a su estado de servidor. Emparejar con NewRecordGridButton y DeleteRecordGridButton en el Buttons fragmento para añadir creación y eliminación de filas a la misma transacción de edición.
<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>Combina el modo ChildContent de lectura de a GridTemplateColumncon un EditChildContent para hacer que una columna sintética o compuesta sea editable. La plantilla de edición recibe — GridTemplateColumnEditContext el mismo contexto de fila que la plantilla de lectura (editCtx.Row.PrimaryRecord, la opcional editCtx.Row.ParentRecord en una SubGrid, editCtx.DependsOnMetadata) más un asistente imperativoeditCtx.SetValue(columnName, value).
EditChildContent solo se activa cuando la cuadrícula es editable (AllowEdit="true" Y el interruptor de Editable está activado) y la fila no está pendiente de eliminación. Las columnas plantilla que omiten EditChildContent permanecen en modo solo lectura incluso en modo edición — útiles para columnas de acciones que siempre deberían renderizar los mismos botones.
El caso más común: la celda se compone a partir de varias columnas encotadas, y quieres que el usuario edite cada una. Coloca los editores de coincidencia <TextEdit> / <NumberEdit> <ColumnEdit> / dentro EditChildContent — el framework encaja el registro principal y la coincidencia EditContextValidator de la fila dentro de ese ámbito, así los editores se registran por sí mismos para el seguimiento sucio y la validación. No hace falta cableado extra.
<GridTemplateColumn
displayName="Nombre completo"
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>;
}}
// Editores de acceso directo — TextEdit se conecta automáticamente al seguimiento sucio
// y la validación mediante el contexto de la fila en cascada.
editRenderer={() => (
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<TextEdit columnName="firstname" displayLabelWhenAvailable={false} />
<TextEdit columnName="lastname" displayLabelWhenAvailable={false} />
</div>
)}
/><GridTemplateColumn Title="Nombre completo"
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>
<!-- Editores de acceso directo — TextEdit se conecta automáticamente al seguimiento sucio
y la validación mediante el contexto de la fila en cascada. -->
<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>Cuando la superficie del editor no se asigna 1:1 a una columna de Dataverse — un único cuadro de texto "Primero Último" que se divide en dos columnas, un deslizador vinculado a varios campos porcentuales, un selector personalizado de rango de fechas que escribe inicio + fin — renderiza tu propia entrada y llama editCtx.SetValue(columnName, value) a cada columna afectada. El framework enruta la escritura a la correcta AliasedTableRecord y se ValueChanged ejecuta, así que el dirty-tracking detecta el cambio.
<GridTemplateColumn
displayName="Nombre completo"
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(' ');
// Widget personalizado — dividido en espacio; Opiniones del consumidor sobre
// validación ya que <Input> No se registra solo.
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="Nombre completo"
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 ?? "");
}
<!-- Widget personalizado — dividido en espacio; Opiniones del consumidor sobre
validación porque FluentTextField no se registra solo. -->
<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>Validación en el camino imperativo
Los editores estándar se
EditChildContentregistraban automáticamente con lasEditContextValidatorfilas y participaban en la puerta de validación antes de guardar del framework. EleditCtx.SetValuecamino evita esa canalización — los valores que no provienen de un editor registrado no se validan. Si tu widget personalizado necesita imponer restricciones, ejecuta dentro de laValueChangedcallback antes de llamarSetValue, o recurre al patrón de editor de entrada.