Tutorial: Creación de un componente conjunto de datos de la aplicación de lienzo
En este tutorial, creará un componente de código de conjunto de datos en la aplicación de lienzo, lo implementará, lo agregará a una pantalla y probará el componente usando Visual Studio Code. El componente de código muestra una cuadrícula conjunto de datos desplazable y paginada que proporciona columnas que se pueden ordenar y filtrar. También permite resaltar filas específicas configurando una columna indicadora. Esta es una solicitud común de los creadores de aplicaciones y puede ser compleja de implementar utilizando componentes de aplicaciones de lienzo nativos. Los componentes de código se pueden escribir para que funcionen tanto en aplicaciones de lienzo como las basadas en modelos. Sin embargo, este componente está escrito para su uso específico dentro de las aplicaciones de lienzo.
Además de estos requisitos, también se asegurará de que el componente de código siga la guía de prácticas recomendadas:
- Uso de Interfaz de usuario fluida de Microsoft
- Localización de las etiquetas de los componentes de código tanto en el diseño como en el runtime
- Asegurarse de que el componente de código se representa con el ancho y el alto proporcionados por la pantalla de la aplicación de lienzo principal
- Consideración para el creador de la aplicación para personalizar la interfaz de usuario utilizando propiedades de entrada y elementos externos de la aplicación en la medida de lo posible
Nota
Antes de comenzar, asegúrese de haber instalado todos los componentes de los requisitos previos.
Código
Puede descargar el ejemplo completo en PowerApps-Samples/component-framework/CanvasGridControl/.
Crea un nuevo proyecto pcfproj
Cree una nueva carpeta para usar con su componente de código. Por ejemplo,
C:\repos\CanvasGrid
.Abra Visual Studio Code y después Archivo > Carpeta abierta y seleccione la carpeta
CanvasGrid
. Si ha agregado las extensiones del Explorador de Windows durante la instalación de Visual Studio Code, puede utilizar la opción Abrir con Code del menú contextual dentro de la carpeta. También puede cargar cualquier carpeta en Visual Studio Code usandocode .
en el símbolo del sistema cuando el directorio actual se establece en esa ubicación.Dentro de una terminal de PowerShell Visual Studio Code (Terminal > Nueva terminal), use el comando pac pcf init para crear un nuevo proyecto de componente de código:
pac pcf init --namespace SampleNamespace --name CanvasGrid --template dataset
o usando la forma corta:
pac pcf init -ns SampleNamespace -n CanvasGrid -t dataset
Esto agrega un nuevo
pcfproj
y archivos relacionados a la carpeta actual, incluyendo unpackages.json
que define los módulos necesarios. Para instalar los módulos obligatorios, use npm install:npm install
Nota
Si recibe el mensaje
The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program.
, asegúrese de haber instalado todos los requisitos previos, específicamente node.js (Se recomienda la versión LTS).
La plantilla incluye un archivo index.ts
junto con varios archivos de configuración. Este es el punto de partida de su componente de código y contiene los métodos de ciclo de vida descritos en Implementación de componentes.
Instalar Interfaz de usuario fluida de Microsoft
Utilizará Microsoft Fluent UI y React para crear interfaces de usuario, por lo que debe instalarlos como dependencias. Utilice lo siguiente en la terminal:
npm install react react-dom @fluentui/react
Esto agrega los módulos a packages.json
y los instala en la carpeta node_modules
. No comprometerá node_modules
en el control de fuente, ya que todos los módulos requeridos se pueden restaurar usando npm install
.
Una de las ventajas de Microsoft Fluent UI es que proporciona una IU coherente y muy accesible.
Configurando eslint
La plantilla utilizada por pac pcf init instala el módulo eslint
a su proyecto y lo configura agregando un archivo .eslintrc.json
. Eslint
ahora requiere la configuración para los estilos de codificación TypeScript y React. Más información: Linting: mejores prácticas y orientación para componentes de código.
Definir las propiedades conjunto de datos
El archivo CanvasGrid\ControlManifest.Input.xml
define los metadatos que describen el comportamiento del componente de código. El atributo control ya tiene el espacio de nombres y el nombre del componente.
Sugerencia
Puede encontrar el XML más fácil de leer si lo formatea para que los atributos aparezcan en líneas separadas. Busque e instale una herramienta de formato XML de su elección en Visual Studio Code Marketplace: Buscar extensiones de formato xml.
Los ejemplos a continuación se han formateado con atributos en líneas separadas para que sean más fáciles de leer.
Debe definir los registros a los que se puede vincular el componente de código, agregando lo siguiente dentro del elemento control
, reemplazando el elemento data-set
existente:
Los registros conjunto de datos estarán vinculados a un origen de datos cuando el componente de código se agregue a una aplicación de lienzo. El conjunto de propiedades indica que el usuario debe configurar una de las columnas de ese conjunto de datos para que se utilice como indicador de resaltado de fila.
Sugerencia
Puede especificar varios elementos del conjunto de datos. Esto podría ser útil si desea buscar un conjunto de datos pero mostrar una lista de registros usando un segundo.
Definición de las propiedades de entrada y salida
Además de conjunto de datos, puede proporcionar las siguientes propiedades de entrada:
HighlightValue
- Permite que el creador de la aplicación proporcione un valor que se comparará con la columna definida comoHighlightIndicator
property-set
. Cuando los valores son iguales, la fila debe resaltarse.HighlightColor
: permite que el creador de aplicaciones seleccione un color para resaltar las filas.
Sugerencia
Se recomienda proporcionar propiedades de entrada para diseñar aspectos comunes de los componentes de su código al crear componentes de código para usar en aplicaciones de lienzo.
Además de las propiedades de entrada, una propiedad de salida nombrada FilteredRecordCount
se actualizará (y activa el evento OnChange
) cuando el recuento de filas cambia debido a una acción de filtro aplicada dentro del componente de código. Esto es útil cuando desea mostrar un mensaje No Rows Found
dentro de la aplicación principal.
Nota
En el futuro, los componentes de código admitirán eventos personalizados para que pueda definir un evento específico en lugar de utilizar el evento genérico OnChange
.
Para definir estas tres propiedades, agregue lo siguiente al archivo CanvasGrid\ControlManifest.Input.xml
, debajo del elemento data-set
:
<property name="FilteredRecordCount"
display-name-key="FilteredRecordCount_Disp"
description-key="FilteredRecordCount_Desc"
of-type="Whole.None"
usage="output" />
<property name="HighlightValue"
display-name-key="HighlightValue_Disp"
description-key="HighlightValue_Desc"
of-type="SingleLine.Text"
usage="input"
required="true"/>
<property name="HighlightColor"
display-name-key="HighlightColor_Disp"
description-key="HighlightColor_Desc"
of-type="SingleLine.Text"
usage="input"
required="true"/>
Guarde este archivo, y luego en la línea de comandos, use:
npm run build
Nota
Si recibe un error como este mientras ejecuta npm run build
:
[2:48:57 PM] [build] Running ESLint...
[2:48:57 PM] [build] Failed:
[pcf-1065] [Error] ESLint validation error:
C:\repos\CanvasGrid\CanvasGrid\index.ts
2:47 error 'PropertyHelper' is not defined no-undef
Abra el archivo index.ts y agregue esto: // eslint-disable-next-line no-undef
, directamente encima de la línea:
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
Luego vuelva a ejecutar el comando npm run build
.
Una vez creado el componente, verá que:
Un archivo generado automáticamente
CanvasGrid\generated\ManifestTypes.d.ts
se agrega a su proyecto. Esto se genera como parte del proceso de construcción desdeControlManifest.Input.xml
y proporciona los tipos para interactuar con las propiedades de entrada / salida.La salida de la compilación se agrega a la carpeta
out
.bundle.js
es el JavaScript transpilado que se ejecuta dentro del navegador, yControlManifest.xml
es una versión reformateada del archivoControlManifest.Input.xml
que se utiliza durante la implementación.Nota
No modifique el contenido de las carpetas
generated
yout
directamente. Se sobrescribirán como parte del proceso de compilación.
Agregar el componente Grid Fluent UI React
Cuando el componente de código usa React, debe haber un solo componente raíz que se representa dentro del método updateView. Dentro de la carpeta CanvasGrid
, agregue un nuevo archivo TypeScript llamado Grid.tsx
y agregue el siguiente contenido:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Overlay } from '@fluentui/react/lib/Overlay';
import {
ScrollablePane,
ScrollbarVisibility
} from '@fluentui/react/lib/ScrollablePane';
import { Stack } from '@fluentui/react/lib/Stack';
import { Sticky } from '@fluentui/react/lib/Sticky';
import { StickyPositionType } from '@fluentui/react/lib/Sticky';
import { IObjectWithKey } from '@fluentui/react/lib/Selection';
import { IRenderFunction } from '@fluentui/react/lib/Utilities';
import * as React from 'react';
type DataSet = ComponentFramework.PropertyHelper.DataSetApi.EntityRecord & IObjectWithKey;
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
}
const onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => {
if (props && defaultRender) {
return (
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>
{defaultRender({
...props,
})}
</Sticky>
);
}
return null;
};
const onRenderItemColumn = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord,
index?: number,
column?: IColumn,
) => {
if (column && column.fieldName && item) {
return <>{item?.getFormattedValue(column.fieldName)}</>;
}
return <></>;
};
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);
const items: (DataSet | undefined)[] = React.useMemo(() => {
setIsLoading(false);
const sortedRecords: (DataSet | undefined)[] = sortedRecordIds.map((id) => {
const record = records[id];
return record;
});
return sortedRecords;
}, [records, sortedRecordIds, hasNextPage, setIsLoading]);
const gridColumns = React.useMemo(() => {
return columns
.filter((col) => !col.isHidden && col.order >= 0)
.sort((a, b) => a.order - b.order)
.map((col) => {
const sortOn = sorting && sorting.find((s) => s.name === col.name);
const filtered =
filtering &&
filtering.conditions &&
filtering.conditions.find((f) => f.attributeName == col.name);
return {
key: col.name,
name: col.displayName,
fieldName: col.name,
isSorted: sortOn != null,
isSortedDescending: sortOn?.sortDirection === 1,
isResizable: true,
isFiltered: filtered != null,
data: col,
} as IColumn;
});
}, [columns, sorting]);
const rootContainerStyle: React.CSSProperties = React.useMemo(() => {
return {
height: height,
width: width,
};
}, [width, height]);
return (
<Stack verticalFill grow style={rootContainerStyle}>
<Stack.Item grow style={{ position: 'relative', backgroundColor: 'white' }}>
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
></DetailsList>
</ScrollablePane>
{(itemsLoading || isComponentLoading) && <Overlay />}
</Stack.Item>
</Stack>
);
});
Grid.displayName = 'Grid';
Nota
El archivo tiene la extensión tsx
que es un archivo TypeScript que admite la sintaxis de estilo XML utilizada por React. Se compila en JavaScript estándar mediante el proceso de compilación.
Notas de diseño de Grid
Esta sección incluye aspectos comunes en el diseño del componente Grid.tsx
.
Es un componente funcional
Este es un componente funcional de React, pero igualmente podría ser un componente de clase. Esto se basa en su estilo de codificación preferido. Los componentes de clase y los componentes funcionales también se pueden mezclar en el mismo proyecto. Tanto los componentes de función como de clase utilizan la sintaxis de estilo tsx
XML utilizada por React. Más información: Componentes funcionales y de clase
Minimizar el tamaño de bundle.js
Al importar los componentes de la UI de Fluent ChoiceGroup
utilizando importaciones basadas en rutas, en lugar de:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
Stack
} from "@fluentui/react";
Este código usa:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Stack } from '@fluentui/react/lib/Stack';
De esta forma, el tamaño del paquete será más pequeño, lo que dará como resultado requisitos de capacidad más bajos y un mejor rendimiento en tiempo de ejecución.
Una alternativa sería utilizar temblor de árboles.
Asignación de desestructuración
Este código:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
Usa Asignación de desestructuración. De esta forma, extrae los atributos requeridos para representar a partir de las propiedades, en lugar de prefijarlos con props.
cada vez que se utilizan.
El código también usa React.memo para encapsular el componente funcional, para que no se represente a menos que alguna de las propiedades de entrada haya cambiado.
Uso de React.useMemo
React.useMemo se utiliza en varios lugares para garantizar que la matriz de elementos creada solo se mute cuando los accesorios de entrada options
o configuration
cambian. Ésta es una práctica recomendada de los componentes de función que reduce las representaciones innecesarias de los componentes secundarios.
Otros elementos a tener en cuenta:
- La
DetailsList
de unaStack
está encapsulada porque luego se agregará un elemento de pie de página con los controles de paginación. - El componente de la interfaz de usuario Fluent
Sticky
se usa para encapsular las columnas de encabezado (usandoonRenderDetailsHeader
) para que permanezcan visibles al desplazarse por la cuadrícula. setKey
se pasa aDetailsList
junto coninitialFocusedIndex
de modo que cuando cambie la página actual, se restablecerán la posición de desplazamiento y la selección.- La función
onRenderItemColumn
se utiliza para representar el contenido de la celda. Acepta el elemento de la fila y usa getFormattedValue para devolver el valor de visualización de la columna. El método getValue devuelve un valor que podría utilizar para proporcionar una representación alternativa. La ventaja degetFormattedValue
es que contiene una cadena formateada para columnas de tipos que no son cadenas, como fechas y búsquedas. - El bloque
gridColumns
está asignando la forma del objeto de las columnas proporcionadas por el contexto conjunto de datos, sobre la forma esperada por la propiedad de columnasDetailsList
. Dado que esto está encapsulado en el enlace de React.useMemo, la salida solo cambiará cuando las propiedadescolumns
osorting
cambien. Puede mostrar los iconos de clasificación y filtrado en las columnas donde los detalles de clasificación y filtrado proporcionados por el contexto del componente de código coinciden con la columna que se está mapeando. Las columnas se ordenan utilizando la propiedadcolumn.order
para asegurarse de que estén en el orden correcto en la cuadrícula, según lo definido por el fabricante de la aplicación. - Mantiene un estado interno para
isComponentLoading
en nuestro componente React. Esto se debe a que cuando el usuario selecciona acciones de clasificación y filtrado, puede atenuar la cuadrícula como una señal visual hasta quesortedRecordIds
se actualiza y se restablece el estado. Hay una propiedad de entrada adicional llamadaitemsLoading
que se asigna a la propiedad dataset.loading proporcionada por el contexto de conjunto de datos. Ambos indicadores se utilizan para controlar la señal de carga visual que se implementa mediante el componenteOverlay
de la interfaz de usuario Fluent.
Actualizar index.ts
El siguiente paso es realizar cambios en el archivo index.ts
para que coincida con las propiedades definidas en Grid.tsx.
Agregar instrucciones de importación e inicializar iconos
Reemplace las importaciones existentes con lo siguiente en el encabezado de index.ts
:
import {IInputs, IOutputs} from './generated/ManifestTypes';
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
Nota
La importación de initializeIcons
es obligatoria porque este código usa el conjunto de iconos Fluent UI. Llame a initializeIcons
para cargar los iconos dentro del arnés de prueba. Dentro de las aplicaciones de lienzo, ya están inicializadas.
Agregar campos a la clase CanvasGrid
Agregue los siguientes campos a la clase CanvasGrid
:
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor() {
}
Actualizar el método init
Agregue lo siguiente a init
:
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary,
container: HTMLDivElement): void {
// Add control initialization code
}
La función init
se llama cuando el componente de código se inicializa por primera vez en la pantalla de una aplicación. Almacena una referencia a lo siguiente:
notifyOutputChanged
: Esta es la devolución de llamada siempre que llame para notificar a la aplicación de lienzo que una de las propiedades ha cambiado.container
: este es el elemento DOM al que se agrega la interfaz de usuario del componente de código.resources
: se utiliza para recuperar cadenas localizadas en el idioma del usuario actual.
context.mode.trackContainerResize(true)) se usa para que updateView
se llame cuando el componente de código cambie de tamaño.
Nota
Actualmente no hay forma de determinar si el componente de código se está ejecutando dentro del arnés de prueba. Debe detectar si el elemento div
de control-dimensions
está presente como indicador.
Actualice el método updateView
Agregue lo siguiente a updateView
:
public updateView(context: ComponentFramework.Context<IInputs>): void {
// Add code to update control view
}
Puede ver que:
- Llama a React.createElement, pasando la referencia al contenedor DOM que recibió dentro de la función
init
. - El componente
Grid
se define dentro deGrid.tsx
y se importa en la parte superior del archivo. allocatedWidth
yallocatedHeight
se proporcionan mediante el contexto principal siempre que cambien (por ejemplo, la aplicación cambia el tamaño del componente de código o usted entra en el modo de pantalla completa) desde que realizó una llamada a trackContainerResize(true) dentro de la funcióninit
.- Puede detectar cuándo hay nuevas filas para mostrar cuando el la matriz updatedProperties contiene la cadena
dataset
. - En el arnés de prueba, la matriz
updatedProperties
no está poblada, por lo que puede usar la banderaisTestHarness
que pone en la funcióninit
para cortocircuitar la lógica que establecensortedRecordId
yrecords
. Mantiene una referencia a los valores actuales hasta que cambian, de modo que no los mute cuando se pasa al componente secundario a menos que se requiera una nueva representación de los datos. - Dado que el componente de código mantiene el estado de la página que se muestra el número de página se restablece cuando el contexto principal restablece los registros a la primera página. Puede saber que está de vuelta a la primera página cuando
hasPreviousPage
es falso.
Actualice el método destroy
Por último, debe ordenar cuando se destruye el componente de código:
Inicie la herramienta de ejecución de pruebas
Asegúrese de que todos los archivos estén guardados y en el terminal use:
npm start watch
Debe establecer el ancho y el alto para ver la cuadrícula del componente de código que se completa con los tres registros de muestra. Luego puede exportar un conjunto de registros a un archivo CSV desde Dataverse y luego cargarlo en la herramienta de pruebas usando Entradas de datos > Panel de registros:
Aquí hay algunos datos de muestra separados por comas que puede guardar en un archivo .csv y usar:
address1_city,address1_country,address1_stateorprovince,address1_line1,address1_postalcode,telephone1,emailaddress1,firstname,fullname,jobtitle,lastname
Seattle,U.S.,WA,7842 Ygnacio Valley Road,12150,555-0112,someone_m@example.com,Thomas,Thomas Andersen (sample),Purchasing Manager,Andersen (sample)
Renton,U.S.,WA,7165 Brock Lane,61795,555-0109,someone_j@example.com,Jim,Jim Glynn (sample),Owner,Glynn (sample)
Snohomish,U.S.,WA,7230 Berrellesa Street,78800,555-0106,someone_g@example.com,Robert,Robert Lyon (sample),Owner,Lyon (sample)
Seattle,U.S.,WA,931 Corte De Luna,79465,555-0111,someone_l@example.com,Susan,Susan Burk (sample),Owner,Burk (sample)
Seattle,U.S.,WA,7765 Sunsine Drive,11910,555-0110,someone_k@example.com,Patrick,Patrick Sands (sample),Owner,Sands (sample)
Seattle,U.S.,WA,4948 West Th St,73683,555-0108,someone_i@example.com,Rene,Rene Valdes (sample),Purchasing Assistant,Valdes (sample)
Redmond,U.S.,WA,7723 Firestone Drive,32147,555-0107,someone_h@example.com,Paul,Paul Cannon (sample),Purchasing Assistant,Cannon (sample)
Issaquah,U.S.,WA,989 Caravelle Ct,33597,555-0105,someone_f@example.com,Scott,Scott Konersmann (sample),Purchasing Manager,Konersmann (sample)
Issaquah,U.S.,WA,7691 Benedict Ct.,57065,555-0104,someone_e@example.com,Sidney,Sidney Higa (sample),Owner,Higa (sample)
Monroe,U.S.,WA,3747 Likins Avenue,37925,555-0103,someone_d@example.com,Maria,Maria Campbell (sample),Purchasing Manager,Campbell (sample)
Duvall,U.S.,WA,5086 Nottingham Place,16982,555-0102,someone_c@example.com,Nancy,Nancy Anderson (sample),Purchasing Assistant,Anderson (sample)
Issaquah,U.S.,WA,5979 El Pueblo,23382,555-0101,someone_b@example.com,Susanna,Susanna Stubberod (sample),Purchasing Manager,Stubberod (sample)
Redmond,U.S.,WA,249 Alexander Pl.,86372,555-0100,someone_a@example.com,Yvonne,Yvonne McKay (sample),Purchasing Manager,McKay (sample)
Nota
Solo se muestra una columna en la herramienta de ejecución de pruebas, independientemente de las columnas que proporcione en el archivo CSV cargado. Esto se debe a que la herramienta de ejecución de pruebas solo muestra property-set
cuando hay uno definido. Si no se define property-set
, las columnas se rellenarán con todas las columnas del archivo CSV cargado.
Agregar selección de fila
Aunque la interfaz de usuario Fluent DetailsList
permite seleccionar registros por defecto, los registros seleccionados no están vinculados a la salida del componente de código. Necesita las propiedades Selected
y SelectedItems
para reflejar los registros elegidos dentro de una aplicación de lienzo, de modo que los componentes relacionados se puedan actualizar. En este ejemplo, permite la selección de un solo elemento a la vez, por lo que SelectedItems
solo contendrá un solo registro.
Actualizar las importaciones de Grid.tsx
Añada lo siguiente a las importaciones en Grid.tsx
:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Overlay } from '@fluentui/react/lib/Overlay';
import {
ScrollablePane,
ScrollbarVisibility
} from '@fluentui/react/lib/ScrollablePane';
import { Stack } from '@fluentui/react/lib/Stack';
import { Sticky } from '@fluentui/react/lib/Sticky';
import { StickyPositionType } from '@fluentui/react/lib/Sticky';
import { IObjectWithKey } from '@fluentui/react/lib/Selection';
import { IRenderFunction } from '@fluentui/react/lib/Utilities';
import * as React from 'react';
Agregar setSelectedRecords a GridProps
A la interfaz GridProps
, dentro de Grid.tsx
, agregue lo siguiente:
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
}
Agregar la propiedad setSelectedRecords a Grid
Dentro del componente de función Grid.tsx
, actualice la desestructuración de props
para agregar el nuevo prop setSelectedRecords
.
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
Directamente debajo, agregue:
const forceUpdate = useForceUpdate();
const onSelectionChanged = React.useCallback(() => {
const items = selection.getItems() as DataSet[];
const selected = selection.getSelectedIndices().map((index: number) => {
const item: DataSet | undefined = items[index];
return item && items[index].getRecordId();
});
setSelectedRecords(selected);
forceUpdate();
}, [forceUpdate]);
const selection: Selection = useConst(() => {
return new Selection({
selectionMode: SelectionMode.single,
onSelectionChanged: onSelectionChanged,
});
});
Los enlaces React.useCallback y useConst garantizan que estos valores no cambien entre las representaciones y provoquen una representación innecesaria de componentes secundarios.
El enlace useForceUpdate garantiza que cuando se actualiza la selección, el componente se vuelve a representar para reflejar el recuento de selección actualizado.
Agregar selección a DetailsList
El objeto selection
creado para mantener el estado de la selección se pasa luego al componente DetailsList
:
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
></DetailsList>
Definir la devolución de llamada setSelectedRecords
Necesita definir la nueva devolución de llamada setSelectedRecords
dentro de index.ts
y páselo al componente Grid
. Cerca de la parte superior de la clase CanvasGrid
, agregue lo siguiente:
export class CanvasGrid
implements ComponentFramework.StandardControl<IInputs, IOutputs>
{
notifyOutputChanged: () => void;
container: HTMLDivElement;
context: ComponentFramework.Context<IInputs>;
sortedRecordsIds: string[] = [];
resources: ComponentFramework.Resources;
isTestHarness: boolean;
records: {
[id: string]: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord;
};
currentPage = 1;
filteredRecordCount?: number;
Nota
El método se define como una función de flecha para vincularlo a instancia this
actual del componente de código.
La llamada a setSelectedRecordIds informa a la aplicación de lienzo que la selección ha cambiado para que otros componentes que hacen referencia SelectedItems
y Selected
se actualicen.
Agregue una nueva devolución de llamada a las propiedades de input
Finalmente, agregue la nueva devolución de llamada a las propiedades de entrada del componente Grid
en el método updateView
:
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
}),
this.container
);
Invocando el evento OnSelect
Hay un patrón en las aplicaciones de lienzo en el que, si una galería o cuadrícula tiene una selección de elemento invocada (por ejemplo, seleccionando un icono de comilla angular), se eleva el evento OnSelect
. Puede implementar este patrón usando el método openDatasetItem del conjunto de datos.
Añadir onNavigate a la interfaz de GridProps
Como antes, agrega un accesorio de devolución de llamada adicional en el componente Grid
agregando lo siguiente a la interfaz GridProps
en Grid.tsx
:
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<
string,
ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
}
Agregar onNavigate a los accesorios de Grid
Nuevamente, debe agregar el nuevo accesorio a la desestructuración de los accesorios:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
} = props;
Agregar onItemInvoked a DetailsList
DetailList
tiene un accesorio de devolución de llamada llamado onItemInvoked
que, a su vez, pasa su devolución de llamada a:
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
></DetailsList>
Agregar el método onNavigate a index.ts
Añade el método onNavigate
para el index.ts
justo debajo del método setSelectedRecords
:
onNavigate = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
): void => {
if (item) {
this.context.parameters.records.openDatasetItem(item.getNamedReference());
}
};
Esto simplemente invoca el método openDatasetItem
en el registro conjunto de datos para que el componente de código aumente el evento OnSelect
. El método se define como una función de flecha para vincularlo a instancia this
actual del componente de código.
Debe pasar esta devolución de llamada a accesorios de componentes Grid
dentro del método updateView
:
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
}),
this.container
);
Cuando guarde todos los archivos, la herramienta de ejecución de pruebas se recargará. Use Ctrl
+ Shift
+ I
(o F12
) y usa Abrir archivo (Ctrl
+ P
) buscando index.ts
y puede colocar un punto de interrupción dentro del método onNavigate
. Hacer doble clic en una fila (o resaltarla con las teclas del cursor y pulsar Enter
) hará que se alcance el punto de interrupción porque DetailsList
invoca la devolución de llamada onNavigate
.
Hay una referencia a _this
porque la función se define como una función de flecha y se ha transpilado en un cierre de JavaScript para capturar la instancia de this
.
Agregar la localización
Antes de continuar, debe agregar cadenas de recursos al componente de código para que pueda usar cadenas localizadas para mensajes, como paginación, clasificación y filtrado. Agrega un nuevo archivo CanvasGrid\strings\CanvasGrid.1033.resx
y usa el editor de recursos de Visual Studio o Visual Studio Code con una extensión para ingresar lo siguiente:
Name | valor |
---|---|
Records_Dataset_Display |
Registros |
FilteredRecordCount_Disp |
Recuento de registros filtrados |
FilteredRecordCount_Desc |
El número de registros después del filtrado |
HighlightValue_Disp |
Resaltar valor |
HighlightValue_Desc |
Se debe resaltar el valor para indicar una fila |
HighlightColor_Disp |
Color de resaltado |
HighlightColor_Desc |
El color para resaltar una fila usando |
HighlightIndicator_Disp |
Resaltar campo indicador |
HighlightIndicator_Desc |
Establezca el nombre del campo para compararlo con el Valor destacado |
Label_Grid_Footer |
Página {0} ({1} seleccionadas) |
Label_SortAZ |
De la A a la Z |
Label_SortZA |
De la Z a la A |
Label_DoesNotContainData |
No contiene datos |
Label_ShowFullScreen |
Mostrar pantalla completa |
Sugerencia
No se recomienda editar archivos resx
directamente. En su lugar, use el editor de recursos de Visual Studio o una extensión para Visual Studio Code. Encuentre una extensión de Visual Studio Code: Busque Visual Studio Marketplace para un editor resx
Los datos para este archivo también se pueden configurar abriendo el archivo CanvasGrid.1033.resx
en el Bloc de notas y copiando el contenido XML a continuación:
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Records_Dataset_Display" xml:space="preserve">
<value>Records</value>
</data>
<data name="FilteredRecordCount_Disp" xml:space="preserve">
<value>Filtered Record Count</value>
</data>
<data name="FilteredRecordCount_Desc" xml:space="preserve">
<value>The number of records after filtering</value>
</data>
<data name="HighlightValue_Disp" xml:space="preserve">
<value>Highlight Value</value>
</data>
<data name="HighlightValue_Desc" xml:space="preserve">
<value>The value to indicate a row should be highlighted</value>
</data>
<data name="HighlightColor_Disp" xml:space="preserve">
<value>Highlight Color</value>
</data>
<data name="HighlightColor_Desc" xml:space="preserve">
<value>The color to highlight a row using</value>
</data>
<data name="HighlightIndicator_Disp" xml:space="preserve">
<value>Highlight Indicator Field</value>
</data>
<data name="HighlightIndicator_Desc" xml:space="preserve">
<value>Set to the name of the field to compare against the Highlight Value</value>
</data>
<data name="Label_Grid_Footer" xml:space="preserve">
<value>Page {0} ({1} Selected)</value>
</data>
<data name="Label_SortAZ" xml:space="preserve">
<value>A to Z</value>
</data>
<data name="Label_SortZA" xml:space="preserve">
<value>Z to A</value>
</data>
<data name="Label_DoesNotContainData" xml:space="preserve">
<value>Does not contain data</value>
</data>
<data name="Label_ShowFullScreen" xml:space="preserve">
<value>Show Full Screen</value>
</data>
</root>
Tiene cadenas de recursos para las propiedades input
/output
y el dataset
y asociado property-set
. Estos se utilizarán en Power Apps Studio en el momento del diseño según el idioma del navegador del fabricante. También puede agregar cadenas de etiquetas que se pueden recuperar en tiempo de ejecución usando getString. Más información: Implementación del componente de API de localización.
Agregue este nuevo archivo de recursos al archivo ControlManifest.Input.xml
dentro del elemento resources
:
Agregar clasificación y filtrado de columnas
Si desea permitir que el usuario ordene y filtre utilizando encabezados de columna de cuadrícula, la interfaz de usuario de Fluent DetailList
proporciona una forma sencilla de agregar menús contextuales a los encabezados de las columnas.
Agregar onSort y onFilter a GridProps
Primero, agregue onSort
y onFilter
a la interfaz GridProps
dentro de Grid.tsx
para proporcionar funciones de devolución de llamada para ordenar y filtrar:
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<
string,
ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
onNavigate: (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
) => void;
}
Agregar onSort, onFilter y recursos a props
Luego, agregue estas nuevas propiedades junto con la referencia resources
(para que pueda recuperar etiquetas localizadas para ordenar y filtrar), a la desestructuración de accesorios:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
} = props;
Importar componentes de ContextualMenu
Necesita agregar algunas importaciones a la parte superior de Grid.tsx
para que pueda usar el componente ContextualMenu
proporcionado por Fluent UI. Puede utilizar importaciones basadas en rutas para reducir el tamaño del paquete.
import { ContextualMenu, DirectionalHint, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu';
Agregue la funcionalidad de representación del menú contextual
Ahora agregue la funcionalidad de renderizado del menú contextual a Grid.tsx
justo debajo de la línea
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);
:
Podrá ver que:
- El estado
contextualMenuProps
controla la visibilidad del menú contextual que se representa con el componenteContextualMenu
de la interfaz de usuario Fluent. - Este código proporciona un filtro simple para mostrar solo valores donde el campo no contiene ningún dato. Puede ampliar esto para proporcionar un filtrado adicional.
- Este código usa
resources.getString
para mostrar etiquetas en el menú contextual que se pueden localizar. - El enlace
React.useCallback
, similar aReact.useMemo
, garantiza que las devoluciones de llamada solo se mutan cuando cambian los valores dependientes. Esto optimiza la representación de componentes secundarios.
Agregue estas nuevas funciones del menú contextual a la selección de columnas y los eventos del menú contextual
Agregue estas nuevas funciones del menú contextual a la selección de columnas y los eventos del menú contextual. Actulice las const gridColumns
para agregar las devoluciones de llamada onColumnContextMenu
y onColumnClick
:
const gridColumns = React.useMemo(() => {
return columns
.filter((col) => !col.isHidden && col.order >= 0)
.sort((a, b) => a.order - b.order)
.map((col) => {
const sortOn = sorting && sorting.find((s) => s.name === col.name);
const filtered =
filtering &&
filtering.conditions &&
filtering.conditions.find((f) => f.attributeName == col.name);
return {
key: col.name,
name: col.displayName,
fieldName: col.name,
isSorted: sortOn != null,
isSortedDescending: sortOn?.sortDirection === 1,
isResizable: true,
isFiltered: filtered != null,
data: col,
} as IColumn;
});
}, [columns, sorting]);
Agregar menú contextual a la salida renderizada
Para que se muestre el menú contextual, debe agregarlo a la salida renderizada. Agregue lo siguiente directamente debajo del componente DetailsList
en la salida devuelta:
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
onItemInvoked={onNavigate}
></DetailsList>
</ScrollablePane>
Agregar funciones onSort y OnFilter
Ahora que ha agregado la interfaz de usuario de clasificación y filtrado, debe agregar las devoluciones de llamada a index.ts
, para realizar la clasificación y el filtrado de los registros vinculados al componente de código. Agregue lo siguiente a index.ts
justo debajo de la función onNavigate
:
onSort = (name: string, desc: boolean): void => {
const sorting = this.context.parameters.records.sorting;
while (sorting.length > 0) {
sorting.pop();
}
this.context.parameters.records.sorting.push({
name: name,
sortDirection: desc ? 1 : 0,
});
this.context.parameters.records.refresh();
};
onFilter = (name: string, filter: boolean): void => {
const filtering = this.context.parameters.records.filtering;
if (filter) {
filtering.setFilter({
conditions: [
{
attributeName: name,
conditionOperator: 12, // Does not contain Data
},
],
} as ComponentFramework.PropertyHelper.DataSetApi.FilterExpression);
} else {
filtering.clearFilter();
}
this.context.parameters.records.refresh();
};
Podrá ver que:
- La ordenación y el filtro se aplican a la propiedad conjunto de datos mediante las propiedades de clasificación y filtrado.
- Al modificar las columnas de clasificación, las definiciones de clasificación existentes deben eliminarse utilizando pop en lugar de reemplazar la matriz de clasificación en sí.
- Debe llamarse a Actualizar después de aplicar la clasificación y el filtrado. Si un filtro y una ordenación se aplican al mismo tiempo, la actualización solo debe invocarse una vez.
Agregue devoluciones de llamada OnSort y OnFilter a la representación de Grid
Por último, puede pasar estas dos devoluciones de llamada a la llamada de representación de Grid
:
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
onNavigate: this.onNavigate,
}),
this.container
);
Nota
En este punto, ya no puede realizar pruebas con la herramienta de prueba porque no proporciona soporte para la clasificación y el filtrado. Más tarde, puede implementar usando pac pcf push y luego agregar a una aplicación de lienzo para probar. Si lo desea, puede saltar a ese paso para ver cómo se ve el componente de código dentro de las aplicaciones de lienzo.
Actualice la propiedad de salida FilteredRecordCount
Dado que la cuadrícula ahora puede filtrar registros internamente, es importante informar a la aplicación de lienzo cuántos registros se muestran. Esto es para que pueda mostrar un mensaje de tipo 'Sin registros'.
Sugerencia
Puede implementar esto internamente dentro del componente de código pero, sin embargo, se recomienda que la mayor parte de la interfaz de usuario se deje en manos de la aplicación de lienzo, ya que le dará al creador de la aplicación más flexibilidad.
Ya ha definido una propiedad de salida llamada FilteredRecordCount
en el ControlManifest.Input.xml
. Cuando se realiza el filtrado y se cargan los registros filtrados, la función updateView
será llamada con una cadena dataset
en la matriz updatedProperties. Si el número de registros ha cambiado, debe llamar a notifyOutputChanged
para que la aplicación de lienzo sepa que debe actualizar los controles que utilizan la propiedad FilteredRecordCount
. Dentro del método updateView
de index.ts
, agregue lo siguiente justo encima de ReactDOM.render
y debajo de allocatedHeight
:
const allocatedHeight = parseInt(
context.mode.allocatedHeight as unknown as string
);
Agregue FilteredRecordCount a getOutputs
Esto actualiza filteredRecordCount
en la clase de componente de código que definió anteriormente cuando es diferente de los nuevos datos recibidos. Después de llamar a notifyOutputChanged
, debe asegurarse de que el valor se devuelva cuando se llama a getOutputs
, así que actualice el método getOutputs
para ser:
Agregar paginación a la cuadrícula
Para grandes conjuntos de datos, las aplicaciones de lienzo dividirán los registros en varias páginas. Puede agregar un pie de página que muestre los controles de navegación de la página. Cada botón se renderizará utilizando una interfaz de usuario Fluent IconButton
, que debe importar.
Agregar IconButton a las importaciones
Añada esto a las importaciones en Grid.tsx
:
import { IconButton } from '@fluentui/react/lib/Button';
Agregar la función stringFormat
El siguiente paso agregará capacidades para cargar el formato para la etiqueta del indicador de página desde las cadenas de recursos ("Page {0} ({1} Selected)"
) y formatea usando una función simple stringFormat
. Esta función podría estar igualmente en un archivo separado y compartido entre sus componentes por conveniencia:
En este tutorial, agréguela en la parte superior de Grid.tsx
, directamente debajo de type DataSet ...
.
function stringFormat(template: string, ...args: string[]): string {
for (const k in args) {
template = template.replace("{" + k + "}", args[k]);
}
return template;
}
Agregar botones de paginación
En Grid.tsx
, agregue el siguiente Stack.Item
debajo del Stack.Item
existente que contiene el ScrollablePane
:
return (
<Stack verticalFill grow style={rootContainerStyle}>
<Stack.Item grow style={{ position: 'relative', backgroundColor: 'white' }}>
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
onItemInvoked={onNavigate}
></DetailsList>
{contextualMenuProps && <ContextualMenu {...contextualMenuProps} />}
</ScrollablePane>
{(itemsLoading || isComponentLoading) && <Overlay />}
</Stack.Item>
</Stack>
);
Podrá ver que:
- El
Stack
asegura que el pie de página se apilará debajo delDetailsList
. El atributogrow
se utiliza para asegurarse de que la cuadrícula se expanda para llenar el espacio disponible. - Carga el formato para la etiqueta del indicador de página desde las cadenas de recursos (
"Page {0} ({1} Selected)"
) y formatea usando la funciónstringFormat
que agregó en el paso anterior. - Puede proporcionar texto
alt
para accesibilidad en la paginaciónIconButtons
. - El estilo del pie de página podría aplicarse igualmente mediante un nombre de clase CSS que hace referencia a un archivo CSS agregado al componente de código.
Agregar props de devolución de llamada para admitir la paginación
A continuación, debe agregar las propiedades de devolución de llamada loadFirstPage
, loadNextPage
y loadPreviousPage
que faltan.
A la interfaz GridProps
, agregue lo siguiente:
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
onNavigate: (item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord) => void;
onSort: (name: string, desc: boolean) => void;
onFilter: (name: string, filtered: boolean) => void;
}
Agregar nuevos props de paginación a Grid
Agregue estas nuevas propiedades a la desestructuración de las propiedades:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
onSort,
onFilter,
resources,
} = props;
Agregar devoluciones de llamada a index.ts
Agregue estas devoluciones de llamada a index.ts
bajo el método onFilter
:
loadFirstPage = (): void => {
this.currentPage = 1;
this.context.parameters.records.paging.loadExactPage(1);
};
loadNextPage = (): void => {
this.currentPage++;
this.context.parameters.records.paging.loadExactPage(this.currentPage);
};
loadPreviousPage = (): void => {
this.currentPage--;
this.context.parameters.records.paging.loadExactPage(this.currentPage);
};
A continuación, actualice la llamada de representación Grid
para incluir estas devoluciones de llamada:
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
onNavigate: this.onNavigate,
onSort: this.onSort,
onFilter: this.onFilter,
}),
this.container
);
Agregar soporte de pantalla completa
Los componentes de código ofrecen la capacidad de mostrarse en modo de pantalla completa. Esto es especialmente útil en tamaños de pantalla pequeños o donde hay espacio limitado para el componente de código dentro de la pantalla de una aplicación de lienzo.
Importar el componente Fluent UI Link
Para iniciar el modo de pantalla completa, puede usar el componente Fluent UI Link
. Agréguelo a las importaciones en la parte superior de Grid.tsx
:
import { Link } from '@fluentui/react/lib/Link';
Agregar el control Link
Para agregar un enlace de pantalla completa, agregue lo siguiente a la Stack
existente que contiene los controles de paginación.
Nota
Asegúrese de agregar esto al Stack
anidado y no al Stack
raíz.
<Stack horizontal style={{ width: '100%', paddingLeft: 8, paddingRight: 8 }}>
<IconButton
alt="First Page"
iconProps={{ iconName: 'Rewind' }}
disabled={!hasPreviousPage}
onClick={loadFirstPage}
/>
<IconButton
alt="Previous Page"
iconProps={{ iconName: 'Previous' }}
disabled={!hasPreviousPage}
onClick={loadPreviousPage}
/>
<Stack.Item align="center">
{stringFormat(
resources.getString('Label_Grid_Footer'),
currentPage.toString(),
selection.getSelectedCount().toString(),
)}
</Stack.Item>
<IconButton
alt="Next Page"
iconProps={{ iconName: 'Next' }}
disabled={!hasNextPage}
onClick={loadNextPage}
/>
</Stack>
Podrá ver que:
- Este código usa recursos para mostrar la etiqueta para respaldar la localización.
- Si el modo de pantalla completa está abierto, no se muestra el enlace. En su lugar, el contexto de la aplicación principal representa automáticamente un icono de cierre.
Agregar props para admitir pantalla completa en GridProps
Agregue los props onFullScreen
y isFullScreen
a la interfaz GridProps
dentro de Grid.tsx
para proporcionar funciones de devolución de llamada para ordenar y filtrar:
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
onNavigate: (item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord) => void;
onSort: (name: string, desc: boolean) => void;
onFilter: (name: string, filtered: boolean) => void;
loadFirstPage: () => void;
loadNextPage: () => void;
loadPreviousPage: () => void;
}
Agregar props para admitir pantalla completa a Grid
Agregue estas nuevas propiedades a la desestructuración de las propiedades:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
onSort,
onFilter,
resources,
loadFirstPage,
loadNextPage,
loadPreviousPage,
} = props;
Actualizar index.ts para admitir la pantalla completa en Grid
Para proporcionar estos nuevos accesorios, en index.ts
, agregue el siguiente método de devolución de llamada bajo loadPreviousPage
:
onFullScreen = (): void => {
this.context.mode.setFullScreen(true);
};
La llamada a setFullScreen hace que el componente de código abra el modo de pantalla completa y ajuste allocatedHeight
y allocatedWidth
en consecuencia, debido a la llamada a trackContainerResize(true)
en el método init
. Una vez que se abre el modo de pantalla completa, se llamará a updateView
, actualizando la representación del componente con el nuevo tamaño. updatedProperties
contiene fullscreen_open
o fullscreen_close
, dependiendo de la transición que se esté produciendo.
Agregue un nuevo campo isFullScreen
a la clase CanvasGrid
en index.ts
para almacenar el estado del modo de pantalla completa:
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
notifyOutputChanged: () => void;
container: HTMLDivElement;
context: ComponentFramework.Context<IInputs>;
sortedRecordsIds: string[] = [];
resources: ComponentFramework.Resources;
isTestHarness: boolean;
records: {
[id: string]: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord;
};
currentPage = 1;
filteredRecordCount?: number;
Edite updateView para rastrear el estado
Agregue lo siguiente al método updateView
para rastrear el estado:
public updateView(context: ComponentFramework.Context<IInputs>): void {
const dataset = context.parameters.records;
const paging = context.parameters.records.paging;
const datasetChanged = context.updatedProperties.indexOf("dataset") > -1;
const resetPaging =
datasetChanged &&
!dataset.loading &&
!dataset.paging.hasPreviousPage &&
this.currentPage !== 1;
if (resetPaging) {
this.currentPage = 1;
}
Pase la devolución de llamada y el campo isFullScreen para representar en la cuadrícula
Ahora puede pasar la devolución de llamada y el campo isFullScreen
en los accesorios de renderizado Grid
:
ReactDOM.render(
React.createElement(Grid, {
width: allocatedWidth,
height: allocatedHeight,
columns: dataset.columns,
records: this.records,
sortedRecordIds: this.sortedRecordsIds,
hasNextPage: paging.hasNextPage,
hasPreviousPage: paging.hasPreviousPage,
currentPage: this.currentPage,
totalResultCount: paging.totalResultCount,
sorting: dataset.sorting,
filtering: dataset.filtering && dataset.filtering.getFilter(),
resources: this.resources,
itemsLoading: dataset.loading,
highlightValue: this.context.parameters.HighlightValue.raw,
highlightColor: this.context.parameters.HighlightColor.raw,
setSelectedRecords: this.setSelectedRecords,
onNavigate: this.onNavigate,
onSort: this.onSort,
onFilter: this.onFilter,
loadFirstPage: this.loadFirstPage,
loadNextPage: this.loadNextPage,
loadPreviousPage: this.loadPreviousPage,
}),
this.container
);
Destacando filas
Ya está listo para agregar la funcionalidad de resaltado de fila condicional. Ya ha definido las propiedades de entrada de HighlightValue
y HighlightColor
, y el HighlightIndicator
property-set
. property-set
permite al creador elegir un campo para usar para comparar con el valor que proporcionan en HighlightValue
.
Importar tipos para admitir el resaltado
La representación de filas personalizadas en DetailsList
requiere algunas importaciones adicionales. Ya hay algunos tipos de @fluentui/react/lib/DetailsList
, así que agregue IDetailsListProps
, IDetailsRowStyles
y DetailsRow
a esa declaración de importación en Grid.tsx
:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps
} from '@fluentui/react/lib/DetailsList';
Ahora, cree el representador de filas personalizado agregando lo siguiente justo debajo del bloque const rootContainerStyle
:
const onRenderRow: IDetailsListProps['onRenderRow'] = (props) => {
const customStyles: Partial<IDetailsRowStyles> = {};
if (props && props.item) {
const item = props.item as DataSet | undefined;
if (highlightColor && highlightValue && item?.getValue('HighlightIndicator') == highlightValue) {
customStyles.root = { backgroundColor: highlightColor };
}
return <DetailsRow {...props} styles={customStyles} />;
}
return null;
};
Podrá ver que:
- Puede recuperar el valor del campo elegido por el fabricante a través del alias
HighlightIndicator
, usando:
item?.getValue('HighlightIndicator')
. - Cuando el valor del campo
HighlightIndicator
coincide con el del valor dehighlightValue
proporcionado por la propiedad de entrada en el componente de código, puede agregar un color de fondo a la fila. - El componente
DetailsRow
es el que utilizaDetailsList
para representar las columnas que definió. No es necesario cambiar el comportamiento que no sea el color de fondo.
Agregue accesorios adicionales para apoyar el resaltado
Agregue algunas propiedades adicionales para highlightColor
y highlightValue
que serán proporcionadas por la representación en el interior de updateView
. Ya los tiene agregados a la interfaz GridProps
, por lo que solo necesita agregarlos a la desestructuración de propiedades:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
onSort,
onFilter,
resources,
loadFirstPage,
loadNextPage,
loadPreviousPage,
onFullScreen,
isFullScreen,
} = props;
Agregue el método onRenderRow a DetailsList
Pase el método onRenderRow
en los accesorios DetailsList
:
<DetailsList
columns={gridColumns}
onRenderItemColumn={onRenderItemColumn}
onRenderDetailsHeader={onRenderDetailsHeader}
items={items}
setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
initialFocusedIndex={0}
checkButtonAriaLabel="select row"
layoutMode={DetailsListLayoutMode.fixedColumns}
constrainMode={ConstrainMode.unconstrained}
selection={selection}
onItemInvoked={onNavigate}
></DetailsList>
Implementación y configuración del componente
Ahora que ha implementado todas las características, debe implementar el componente de código en Microsoft Dataverse para las pruebas.
Dentro de su entorno de Dataverse, asegúrese de que haya un editor creado con un prefijo de
samples
:Esto también podría ser su propio editor, siempre que actualice el parámetro de prefijo del editor en la llamada a pac pcf push a continuación. Más información: Crear un editor de soluciones.
Una vez que haya guardado el editor, estará listo para autorizar la CLI en su entorno para que podamos enviar el componente de código compilado. En la línea de comandos, use:
pac auth create --url https://myorg.crm.dynamics.com
Reemplace
myorg.crm.dynamics.com
con la URL de su propio entorno de Dataverse. Inicie sesión con un usuario Administrador / personalizador cuando se le solicite. Los privilegios proporcionados por estos roles de usuario son necesarios para implementar cualquier componente de código en Dataverse.Para implementar su componente de código, use:
pac pcf push --publisher-prefix samples
Nota
Si recibe el error
Missing required tool: MSBuild.exe/dotnet.exe. Please add MSBuild.exe/dotnet.exe in Path environment variable or use 'Developer Command Prompt for VS
, debe instalar Visual Studio 2019 para Windows y Mac o Herramientas de compilación para Visual Studio 2019, asegurándose de seleccionar la carga de trabajo 'Herramientas de compilación de .NET' como se describe en los requisitos previos.Una vez completado, este proceso habrá creado una pequeña solución temporal llamada PowerAppTools_samples en su entorno, y el componente de código
CanvasGrid
se agregará a esta solución. Puede mover el componente de código a su propia solución más adelante si es necesario. Más información: Componente de código de Administración del ciclo de vida de las aplicaciones (ALM).Para usar componentes de código dentro de las aplicaciones de lienzo, debe habilitar Power Apps component framework para aplicaciones de lienzo en el entorno que está utilizando.
a. Abra el Centro de administración (admin.powerplatform.microsoft.com) y navegue hasta su entorno. b. Vaya a Configuración > Producto > Características. Asegúrese de que Power Apps component framework para aplicaciones de lienzo está Activado:
Cree una nueva aplicación de lienzo utilizando un diseño Tableta.
Desde el panel Insertar, seleccione Obtenga más componentes.
Seleccione la pestaña Código en el panel Importar componentes.
Seleccione el componente
CanvasGrid
.Seleccione Importar. El componente de código ahora aparecerá en Componentes de código en el panel Insertar.
Arrastre el compoente
CanvasGrid
en la pantalla y enlace con la tablaContacts
en Microsoft Dataverse.Establezca las siguientes propiedades en el componente de código
CanvasGrid
usando el panel de propiedades:- Resaltar valor =
1
: este es el valor que tienestatecode
cuando el registro está inactivo. - Resaltar color =
#FDE7E9
: este es el color que se utilizará cuando el registro esté inactivo. HighlightIndicator
="statecode"
- Este es el campo con el que comparar. Esto estará en el panel Avanzado en la sección DATOS.
- Resaltar valor =
Agregue un nuevo componente
TextInput
y llámelotxtSearch
.Actualice la propiedad
CanvasGrid.Items
para serSearch(Contacts,txtSearch.Text,"fullname")
.A medida que escriba en la Entrada de texto, verá que los contactos se filtran en la cuadrícula.
Agregue una nueva Etiqueta de texto y establezca el texto en "No se encontraron registros". Coloque la etiqueta en la parte superior de la cuadrícula de lienzo.
Establezca la propiedad Visible de la etiqueta Texto en
CanvasGrid1.FilteredRecordCount=0
.
Esto significa que cuando no haya registros que coincidan con el valor txtSearch
, o si se aplica un filtro de columna utilizando el menú contextual que no devuelve registros (por ejemplo, el nombre completo no contiene datos), se mostrará la etiqueta.
Agregue un Formulario de visualización (desde el grupo Entrada en el panel Insertar).
Establezca el formulario
DataSource
hacia la tablaContacts
y agregue algunos campos de formulario.Establezca la propiedad del formulario
Item
enCanvasGrid1.Selected
.Ahora debería ver que cuando selecciona elementos en la cuadrícula, el formulario muestra el elemento seleccionado.
Agregue una Pantalla nueva a la aplicación de lienzo llamada
scrDetails
.Copie el formulario de la pantalla anterior y péguelo en la nueva pantalla.
Establezca la propiedad
CanvasGrid1.OnSelect
enNavigate(scrDetails)
.Cuando invoca la acción de selección de fila de la cuadrícula, ahora debería ver que la aplicación navega a la segunda pantalla con el elemento seleccionado.
Depurar después de implementar
Puede depurar fácilmente su componente de código mientras se ejecuta en la aplicación de lienzo abriendo las Herramientas de desarrollo con Ctrl+Shift+I
.
Seleccione Ctrl+P
y escriba Grid.tsx
o Index.ts
. A continuación, puede establecer un punto de interrupción y recorrer su código.
Si necesita realizar más cambios en su componente, no necesita implementarlo cada vez. En su lugar, utilice la técnica descrita en Componentes del código de depuración para crear un Fiddler AutoResponder para cargar el archivo desde su sistema de archivos local mientras se ejecuta npm start watch
.
La Respuesta automática se vería similar a lo siguiente:
REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.CanvasGrid[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\CanvasGrid\out\controls\CanvasGrid\${folder}\${fname}
También necesitará habilitar los filtros para agregar el encabezado Access-Control-Allow-Origin
. Más información: Depuración después de implementar en Microsoft Dataverse.
Necesitara Caché vacío y actualización completa en la sesión de su navegador para que el archivo AutoResponder sea recogido. Una vez cargado, simplemente puede actualizar el navegador, ya que Fiddler agregará un encabezado de control de caché al archivo para evitar que se almacene en caché.
Una vez que esté satisfecho con sus cambios, puede incrementar la versión del parche en el manifiesto y luego volver a implementar usando pac pcf push..
Hasta ahora, ha implementado una compilación de desarrollo, que no está optimizada y se ejecutará más lentamente en el tiempo de ejecución. Puede optar por implementar una compilación optimizada utilizando pac pcf push editando el CanvasGrid.pcfproj
. Debajo de OutputPath
, agregue lo siguiente: <PcfBuildMode>production</PcfBuildMode>
<PropertyGroup>
<Name>CanvasGrid</Name>
<ProjectGuid>a670bba8-e0ae-49ed-8cd2-73917bace346</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
</PropertyGroup>
Artículos relacionados
Administración del ciclo de vida de la aplicación (ALM) con Microsoft Power Platform
Referencia de la API de Power Apps component framework
Crear el primer componente
Depurar componentes de código
Nota
¿Puede indicarnos sus preferencias de idioma de documentación? Realice una breve encuesta. (tenga en cuenta que esta encuesta está en inglés)
La encuesta durará unos siete minutos. No se recopilan datos personales (declaración de privacidad).