Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tutorial, crearás un componente de código para el conjunto de datos de una aplicación de lienzo, lo implementarás, lo agregarás a una pantalla y probarás el componente mediante Visual Studio Code. El componente de código muestra una cuadrícula de conjunto de datos paginada y desplazable que proporciona columnas ordenables y filtrables. También permite el resaltado de filas específicas mediante la configuración de una columna de indicador. 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 trabajar tanto en aplicaciones basadas en lienzo como en aplicaciones controladas por modelos. Sin embargo, este componente está diseñado para usarse específicamente dentro de las aplicaciones canvas.
Además de estos requisitos, también garantizará que el componente de código siga las instrucciones de procedimientos recomendados:
- Uso de la interfaz de usuario de Microsoft Fluent
- Localización de las etiquetas de componentes de código en el diseño y el entorno de ejecución
- 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 que el creador de aplicaciones personalice la interfaz de usuario mediante propiedades de entrada y elementos de aplicación externos en la medida de lo posible
Nota:
Antes de empezar, asegúrese de que ha instalado todos los componentes de requisitos previos.
Código
Puede descargar el ejemplo completo de PowerApps-Samples/component-framework/CanvasGridControl/.
Creación de un nuevo pcfproj proyecto
Cree una nueva carpeta que se usará para el componente de código. Por ejemplo:
C:\repos\CanvasGrid.Abra Visual Studio Code y, después, File>Abrir carpeta y seleccione la carpeta
CanvasGrid. Si ha agregado las extensiones Windows Explorer durante la instalación de Visual Studio Code, puede usar la opción de menú contextual Open with Code dentro de la carpeta. También puede cargar cualquier carpeta en Visual Studio Code mediantecode .en el símbolo del sistema cuando el directorio actual esté establecido en esa ubicación.Dentro de un nuevo terminal de PowerShell Visual Studio Code (Terminal>New 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 dataseto usando la forma corta:
pac pcf init -ns SampleNamespace -n CanvasGrid -t datasetEsto agrega archivos nuevos
pcfprojy relacionados a la carpeta actual, incluido unpackages.jsonque define los módulos necesarios. Para instalar los módulos necesarios, use npm install:npm installNota:
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 que ha instalado todos los requisitos previos, específicamente node.js (se recomienda la versión de LTS).
La plantilla incluye un index.ts archivo junto con varios archivos de configuración. Este es el punto de partida del componente de código y contiene los métodos de ciclo de vida descritos en Implementación de componentes.
Instalación de la interfaz de usuario de Microsoft Fluent
Usará la interfaz de usuario de Microsoft Fluent y React para crear la interfaz de usuario, por lo que debe instalarlas como dependencias. Use lo siguiente en el terminal:
npm install react react-dom @fluentui/react
Esto agrega los módulos a packages.json e los instala en la node_modules carpeta . No vas a confirmar node_modules en el control de versiones ya que todos los módulos necesarios se pueden restaurar mediante npm install.
Una de las ventajas de la interfaz de usuario de Microsoft Fluent es que proporciona una interfaz de usuario coherente y altamente accesible .
Configuración eslint
La plantilla usada por pac pcf init instala el módulo en el eslint proyecto y lo configura agregando un .eslintrc.json archivo.
Eslint ahora requiere configurar los estilos de codificación TypeScript y React. Más información: Linting: procedimientos recomendados e instrucciones para componentes de código.
Definición de las propiedades del conjunto de datos
El CanvasGrid\ControlManifest.Input.xml archivo define los metadatos que describen el comportamiento del componente de código. El atributo de control ya contendrá 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 que prefiera en Visual Studio Code Marketplace: Search para 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 enlazar el componente de código; para ello, agregue lo siguiente dentro del control elemento y reemplace el elemento existente data-set :
<data-set name="sampleDataSet"
display-name-key="Dataset_Display_Key">
</data-set>
El conjunto de datos de registros se enlazará 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 que se van a usar como indicador de resaltado de fila.
Sugerencia
Puede especificar varios elementos del conjunto de datos. Esto podría ser útil si desea buscar un dataset, pero mostrar una lista de registros utilizando un segundo conjunto de datos.
Definición de las propiedades de entrada y salida
Además del conjunto de datos, puede proporcionar las siguientes propiedades de entrada :
-
HighlightValue: permite que el creador de aplicaciones proporcione un valor que se va a comparar con la columna definida comoHighlightIndicatorproperty-set. Cuando los valores son iguales, la fila debe resaltarse. -
HighlightColor- Permite que el creador de la aplicación seleccione un color que se usará para resaltar las filas.
Sugerencia
Al crear componentes de código para usarlos en aplicaciones tipo canvas, se recomienda proporcionar propiedades de entrada para el estilo de los aspectos comunes de sus componentes de código.
Además de las propiedades de entrada, se actualizará una propiedad de salida denominada FilteredRecordCount (y desencadenará el OnChange evento) cuando se cambie el recuento de filas debido a una acción de filtro aplicada dentro del componente de código. Esto resulta útil cuando desea mostrar un No Rows Found mensaje dentro de la aplicación primaria.
Nota:
En el futuro, los componentes de código admitirán eventos personalizados para que pueda definir un evento específico en lugar de usar el evento genérico OnChange .
Para definir estas tres propiedades, agregue lo siguiente al CanvasGrid\ControlManifest.Input.xml archivo, debajo del data-set elemento :
<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, a continuación, en la línea de comandos, use:
npm run build
Nota:
Si recibe un error similar al siguiente al ejecutar 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 index.ts archivo y agregue esto: // eslint-disable-next-line no-undef, directamente encima de la línea:
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
Ejecuta npm run build nuevamente.
Una vez compilado el componente, verá lo siguiente:
Se agrega un archivo
CanvasGrid\generated\ManifestTypes.d.tsgenerado automáticamente al proyecto. Esto se genera como parte del proceso de compilación a partir deControlManifest.Input.xmly proporciona los tipos para interactuar con las propiedades de entrada y salida.La salida de compilación se agrega a la
outcarpeta .bundle.jses el JavaScript transpilado que se ejecuta dentro del explorador yControlManifest.xmles una versión reformateada del archivo que se usa durante la implementaciónControlManifest.Input.xml.Nota:
No modifique directamente el contenido de las
generatedcarpetas yout. Se sobrescribirán como parte del proceso de compilación.
Adición del componente Grid Fluent UI React
Cuando el componente de código usa React, debe haber un único componente raíz que se represente en el método updateView. Dentro de la CanvasGrid carpeta , agregue un nuevo archivo TypeScript denominado Grid.tsxy 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 usada por React. Se compila en JavaScript estándar mediante el proceso de compilación.
Notas de diseño de cuadrícula
En esta sección se incluyen comentarios sobre el diseño del Grid.tsx componente.
Es un componente funcional
Se trata de un componente funcional de React, pero también 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 usan la tsx sintaxis de estilo XML usada por React. Más información: Componentes de función y clase
Minimizar el tamaño de bundle.js
Al importar los componentes Fluent UI de la ChoiceGroup interfaz de usuario mediante 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 este modo, el tamaño de la agrupación será menor, lo que dará lugar a requisitos de capacidad más bajos y un mejor rendimiento en tiempo de ejecución.
Una alternativa sería usar la agitación 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 la asignación de estructuración. De este modo, se extraen los atributos necesarios para representar desde las propiedades, en lugar de prefijarlos con props. cada vez que se usan.
El código también usa React.memo para envolver el componente funcional de modo que no se renderice a menos que alguna de las propiedades de entrada haya cambiado.
Uso de React.useMemo
React.useMemo se usa en varios lugares para asegurarse de que la matriz de elementos creada solo se muta cuando la propiedad options de entrada o configuration cambia. É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:
- En un
DetailsList, elStackse ajusta porque más adelante se agregará un elemento de pie de página con los controles de paginación. - El componente fluent UI
Stickyse usa para encapsular las columnas de encabezado (medianteonRenderDetailsHeader) para que permanezcan visibles al desplazarse por la cuadrícula. -
setKeyse pasa alDetailsListjunto coninitialFocusedIndexpara que cuando cambie la página actual, se restablecerá la posición de desplazamiento y la selección. - La función
onRenderItemColumnse usa para representar el contenido de la celda. Acepta el elemento de fila y usa getFormattedValue para devolver el valor para mostrar de la columna. El método getValue devuelve un valor que puede usar para proporcionar una representación alternativa. La ventaja degetFormattedValuees que contiene una cadena con formato para columnas de tipos que no son de cadena, como fechas y búsquedas. - El
gridColumnsbloque asigna la forma de objeto de las columnas proporcionadas por el contexto del conjunto de datos a la forma esperada por elDetailsListprop de columnas. Dado que esto se ajusta en el hook React.useMemo, la salida solo cambiará cuando cambien loscolumnsosortingprops. Puede mostrar los iconos de ordenación y filtro en las columnas donde los detalles de ordenación y filtrado proporcionados por el contexto del componente de código coinciden con la columna que se asigna. Las columnas se ordenan mediante lacolumn.orderpropiedad para asegurarse de que están en el orden correcto en la cuadrícula, tal como lo define el creador de la aplicación. - Estás manteniendo un estado interno para
isComponentLoadingen nuestro componente de React. Esto es porque cuando el usuario selecciona acciones de ordenación y filtrado, puede dejar la cuadrícula en gris como señal visual hasta que la cuadrícula se actualice y el estado se restablezca. Hay una propiedad de entrada adicional denominadaitemsLoadingque se asigna a la propiedad dataset.loading proporcionada por el contexto del conjunto de datos. Ambas banderas se utilizan para controlar la indicación visual de carga que se implementa mediante el componente Fluent UIOverlay.
Actualizar index.ts
El siguiente paso es realizar cambios en el index.ts archivo para que coincidan con las propiedades definidas en Grid.tsx.
Agregar instrucciones de importación e inicializar iconos
Para el encabezado de index.ts, reemplace las importaciones existentes por lo siguiente:
import {IInputs, IOutputs} from './generated/ManifestTypes';
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
Nota:
La importación de initializeIcons es necesaria porque este código usa el conjunto de iconos Fluent UI. Llamas 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 CanvasGrid clase :
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor() {
}
Actualización del 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
}
Se llama a la función init cuando el componente de código se inicializa por primera vez en una pantalla de la app. Puede almacenar una referencia a lo siguiente:
-
notifyOutputChanged: Este es el callback que se proporciona para que lo llames y notifiques a la app 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 usa para recuperar cadenas localizadas en el idioma del usuario actual.
Se usa context.mode.trackContainerResize(true)) para que updateView se llame cuando el componente de código cambie el tamaño.
Nota:
Actualmente, no hay ninguna manera de determinar si el componente de código se ejecuta dentro del arnés de pruebas. Debe detectar si el control-dimensionsdiv elemento está presente como indicador.
Actualizar el método updateView
Agregue lo siguiente a updateView:
public updateView(context: ComponentFramework.Context<IInputs>): void {
// Add code to update control view
}
Puede ver lo siguiente:
- Llame a React.createElement y pase la referencia al contenedor DOM que recibió dentro de la
initfunción. - El componente
Gridse define dentro deGrid.tsxy se importa en la parte superior del archivo. - El
allocatedWidthyallocatedHeightlo proporcionará el contexto primario cada vez que cambien (por ejemplo, la aplicación cambia el tamaño del componente de código o entras en modo de pantalla completa), ya que dentro de la función realizaste una llamada ainit. - Puede detectar cuándo hay nuevas filas que mostrar cuando la matriz updatedProperties contiene la
datasetcadena. - En el arnés de prueba, la
updatedPropertiesmatriz no se rellena, por lo que puede usar laisTestHarnessmarca que usted establece en la funcióninitpara anular la lógica que establecesortedRecordIdyrecords. Se mantiene una referencia a los valores actuales hasta que cambian, de modo que no se mutan cuando se pasan 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 primario restablece los registros a la primera página. Sabes cuándo vuelves a la primera página cuando
hasPreviousPagees false.
Actualización del método destroy
Por último, debe ordenar cuando se destruye el componente de código:
public destroy(): void {
// Add code to cleanup control if necessary
}
Inicie la herramienta de ejecución de pruebas
Asegúrese de que todos los archivos se guarden 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. A continuación, puede exportar un conjunto de registros a un archivo CSV desde Dataverse y luego cargarlo en el arnés de pruebas usando el panel de Registros de >:
Estos son algunos datos de ejemplo 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 hay una sola columna que se muestra en el arnés de pruebas independientemente de las columnas que proporcione en el archivo CSV cargado. Esto se debe a que el arnés de prueba solo se muestra property-set cuando hay uno definido. Si no se define property-set, se rellenarán todas las columnas del archivo CSV cargado.
Agregar selección de fila
Aunque la interfaz de usuario DetailsList de Fluent permite seleccionar registros de forma predeterminada, los registros seleccionados no están vinculados a la salida del componente de código. Necesita que las propiedades Selected y SelectedItems reflejen los registros elegidos dentro de una aplicación canvas, de modo que se puedan actualizar los componentes relacionados. En este ejemplo, solo se permite la selección de un solo elemento a la vez, por lo que SelectedItems solo contendrá un único registro.
Actualizar las importaciones de Grid.tsx
Agregue lo siguiente a las importaciones dentro de 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
En la interfaz GridProps, dentro 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;
}
Agregue la propiedad setSelectedRecords a Grid
Dentro del componente de función Grid.tsx, actualice la desestructuración de props para agregar la nueva propiedad setSelectedRecords.
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
Directamente debajo de eso, 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 React.useCallback y useConst hooks garantizan que estos valores no muten entre renderizaciones y eviten la renderización innecesaria de componentes hijos.
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 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
Debe definir la nueva devolución de llamada setSelectedRecords dentro de index.ts y pasarla al componente Grid. Cerca de la parte superior de la CanvasGrid clase, 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 enlazarlo a la instancia actual this del componente de código.
La llamada a setSelectedRecordIds informa a la aplicación de lienzo que la selección ha cambiado, de modo que otros componentes que hagan referencia a SelectedItems y Selected se actualizarán.
Agregue una nueva devolución de llamada a las propiedades de input
Por último, agregue la nueva devolución de llamada a las propiedades de entrada del componente
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
);
Invocación del 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 elementos invocada (por ejemplo, al seleccionar un icono de cheurón), se activa el evento OnSelect. Puede implementar este patrón mediante el método openDatasetItem del conjunto de datos.
Adición de onNavigate a la interfaz GridProps
Como antes, agregue una propiedad de devolución de llamada adicional en el componente Grid agregando lo siguiente a la interfaz GridProps dentro de 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 una propiedad de devolución de llamada denominada onItemInvoked que, a su vez, pasa la 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>
Añadir el método onNavigate a index.ts
Agregue el onNavigate método al index.ts método justo debajo del setSelectedRecords método :
onNavigate = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
): void => {
if (item) {
this.context.parameters.records.openDatasetItem(item.getNamedReference());
}
};
Esto simplemente invoca el openDatasetItem método en el registro del conjunto de datos para que el componente de código genere el OnSelect evento. El método se define como una función de flecha para enlazarlo a la instancia actual this del componente de código.
Debe pasar esta devolución de llamada a las propiedades del Grid componente dentro del updateView método :
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
);
Al guardar todos los archivos, el arnés de pruebas se volverá a cargar. Utilice Ctrl + Shift + I (o F12) y seleccione Abrir archivo (Ctrl + P) para buscar index.ts y puede colocar un punto de interrupción dentro del método onNavigate. Haga doble clic en una fila (o resáltela con las teclas de cursor y presionando Enter) y el proceso alcanzará el punto de interrupción porque DetailsList realizará la llamada de retorno 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 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, ordenación y filtrado. Agregue un nuevo archivo CanvasGrid\strings\CanvasGrid.1033.resx y use el editor de recursos de Visual Studio o Visual Studio Code con una extensión para escribir lo siguiente:
| Nombre | Importancia |
|---|---|
Records_Dataset_Display |
Registros |
FilteredRecordCount_Disp |
Recuento de registros filtrados |
FilteredRecordCount_Desc |
Número de registros después del filtrado |
HighlightValue_Disp |
Valor de resaltado |
HighlightValue_Desc |
El valor que indica que una fila debe ser resaltada |
HighlightColor_Disp |
Color de resaltado |
HighlightColor_Desc |
El color para resaltar una fila usando |
HighlightIndicator_Disp |
Resaltar campo indicador |
HighlightIndicator_Desc |
Configure el nombre del campo para compararlo con el Valor de Resaltado. |
Label_Grid_Footer |
Página {0} ({1} seleccionada) |
Label_SortAZ |
De la A a la Z |
Label_SortZA |
de Z a A |
Label_DoesNotContainData |
No contiene datos |
Label_ShowFullScreen |
Mostrar pantalla completa |
Sugerencia
No se recomienda editar resx archivos directamente. En su lugar, use el editor de recursos de Visual Studio o una extensión para Visual Studio Code. Busque una extensión de Visual Studio Code: Busque en Visual Studio Marketplace para encontrar un editor de resx
Los datos de este archivo también se pueden establecer abriendo el archivo en el CanvasGrid.1033.resx Bloc de notas y copiando el contenido XML siguiente:
<?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 input/output propiedades y dataset y asociadas property-set. Estos se usarán en Power Apps Studio en tiempo de diseño en función del lenguaje del explorador del creador. También puede agregar cadenas de etiqueta que se pueden recuperar en tiempo de ejecución mediante getString. Más información: Implementación del componente de la API de localización.
Agregue este nuevo archivo de recursos al ControlManifest.Input.xml archivo dentro del resources elemento :
<resources>
<code path="index.ts"
order="1" />
</resources>
Agregar ordenación y filtrado de columnas
Si desea permitir que el usuario ordene y filtre mediante encabezados de columna de cuadrícula, la interfaz de usuario DetailList de Fluent proporciona una manera sencilla de agregar menús contextuales a los encabezados de columna.
Agregar onSort y onFilter a GridProps
En primer lugar, agregue onSort y onFilter a la interfaz GridProps en 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
A continuación, agregue estas nuevas propiedades junto con la referencia resources (de modo que pueda recuperar etiquetas localizadas para ordenar y filtrar) 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,
} = props;
Importación de componentes ContextualMenu
Debe agregar algunas importaciones a la parte superior de Grid.tsx para que pueda usar el ContextualMenu componente proporcionado por fluent UI. Puede usar importaciones basadas en rutas para reducir el tamaño del paquete.
import { ContextualMenu, DirectionalHint, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu';
Adición de la funcionalidad de representación del menú contextual
Ahora, agregue la funcionalidad de representación del menú contextual a Grid.tsx justo debajo de la línea.
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);:
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);
Verá lo siguiente:
- El
contextualMenuPropsestado controla la visibilidad del menú contextual que se representa mediante el componente fluent UIContextualMenu. - Este código proporciona un filtro sencillo para mostrar solo los valores en los que el campo no contiene ningún dato. Puede ampliar esto para proporcionar filtrado adicional.
- Este código usa
resources.getStringpara mostrar etiquetas en el menú contextual que pueden localizarse. - El
React.useCallbackhook, similar aReact.useMemo, garantiza que los callbacks solo cambian cuando cambian los valores dependientes. Esto optimiza la representación de componentes secundarios.
Agregar nuevas funciones de menú contextual a los eventos de menú contextual y selección de columna
Agregue estas nuevas funciones de menú contextual a los eventos de menú contextual y selección de columna. Actualice el const gridColumns para agregar las devoluciones de llamada de 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 representada
Para que se muestre el menú contextual, debe agregarlo a la salida representada. Agregue lo siguiente directamente debajo del DetailsList componente 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 has agregado la interfaz de usuario de clasificación y filtro, necesitas agregar las funciones de callback a index.ts para realizar la clasificación y el filtrado en los registros enlazados al componente de código. Agregue lo siguiente a index.ts justo debajo de la onNavigate función :
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();
};
Verá lo siguiente:
- La ordenación y el filtro se aplican al conjunto de datos mediante las propiedades de ordenación y filtrado .
- Al modificar las columnas de ordenación, las definiciones de ordenación existentes deben quitarse mediante la función 'pop', en lugar de reemplazar la matriz de ordenación.
- Se debe llamar a Refresh después de aplicar la ordenación y el filtrado. Si se aplica un filtro y una ordenación al mismo tiempo, solo es necesario llamar a la actualización 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 Grid llamada de renderizado.
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 momento, ya no puede probar con el arnés de pruebas porque no proporciona compatibilidad con la ordenación y el filtrado. Más adelante, puede implementar usando pac pcf push y, a continuación, agregar a una aplicación de lienzo para pruebas. Si lo desea, puede avanzar directamente a ese paso para ver cómo se ve el componente de código dentro de las aplicaciones de Canvas.
Actualiza 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 denominada FilteredRecordCount en .ControlManifest.Input.xml Cuando se realiza el filtrado y se cargan los registros filtrados, se llamará a la updateView función con cadena dataset en la matriz updatedProperties . Si el número de registros ha cambiado, debe realizar una llamada a notifyOutputChanged para que la aplicación Canvas sepa que debe actualizar los controles que utilicen la propiedad FilteredRecordCount. Dentro del updateView método 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
);
Agregar el FilteredRecordCount a getOutputs
Esto actualiza la clase de componente de código filteredRecordCount que usted definió anteriormente, cuando estos son diferentes de los nuevos datos recibidos. Después de llamar a notifyOutputChanged, debe asegurarse de que se devuelva el valor cuando se llame a getOutputs, por lo tanto, debe actualizar el método getOutputs para que sea:
public getOutputs(): IOutputs {
return {};
}
Agregar paginación a la cuadrícula
En el caso de grandes conjuntos de datos, las aplicaciones canvas separarán los registros en múltiples páginas. Puede agregar un pie de página que muestre los controles de navegación de página. Cada botón se representará mediante una interfaz de usuario IconButtonde Fluent, que debe importar.
Agregar IconButton a las importaciones
Agregue esto a las importaciones dentro de Grid.tsx.
import { IconButton } from '@fluentui/react/lib/Button';
Agregar función stringFormat
En el siguiente paso se agregarán capacidades para cargar el formato de la etiqueta del indicador de página desde las cadenas de recursos ("Page {0} ({1} Selected)") y formatear usando una función sencilla stringFormat. Esta función podría estar igualmente en un archivo independiente y compartirse entre los componentes para mayor comodidad:
En este tutorial, agréguelo en la parte superior de Grid.tsx, justo 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 lo siguiente Stack.Item debajo del existente Stack.Item que contiene :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>
);
Verá lo siguiente:
-
Stackgarantiza que el pie de página se apilará debajo deDetailsList. Elgrowatributo se usa para asegurarse de que la cuadrícula se expande para rellenar el espacio disponible. - Cargas el formato de la etiqueta de indicador de página de las cadenas de recursos (
"Page {0} ({1} Selected)") y lo estructuras utilizando la funciónstringFormatque añadiste en el paso anterior. - Puede proporcionar
alttexto para accesibilidad en la paginaciónIconButtons. - El estilo del pie de página podría aplicarse igualmente mediante un nombre de clase CSS que haga 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 los props de devolución de llamada loadFirstPage, loadNextPage y loadPreviousPage que faltan.
En 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 debajo del 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);
};
Luego, actualice la llamada de Grid para incluir estos callbacks:
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
);
Adición de compatibilidad con pantalla completa
Los componentes de código ofrecen la capacidad de mostrarse en modo de pantalla completa. Es especialmente útil en tamaños de pantalla pequeños o donde hay espacio limitado para el componente de código dentro de una pantalla de aplicación de tipo lienzo.
Importar componente de 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 vínculo de pantalla completa, agregue lo siguiente al existente Stack que contiene los controles de paginación.
Nota:
Asegúrese de agregar esto al anidado Stack y no a la raíz Stack.
<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>
Verá lo siguiente:
- Este código usa recursos para mostrar la etiqueta para admitir la localización.
- Si el modo de pantalla completa está abierto, no se muestra el vínculo. En su lugar, el contexto de la aplicación principal representa automáticamente un icono de cierre.
Agregar propiedades para permitir compatibilidad con pantalla completa en GridProps
Agrega los props onFullScreen y isFullScreen a la interfaz GridProps dentro de Grid.tsx para ofrecer funciones de callback que ordenen y filtren.
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 estas nuevas propiedades, dentro de index.ts, agregue el siguiente método de devolución de llamada 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 abierto el modo de pantalla completa, se llamará aupdateView, actualizando el renderizado del componente con el nuevo tamaño.
updatedProperties contiene fullscreen_open o fullscreen_close, en función de la transición que se está produciendo.
Para almacenar el estado del modo de pantalla completa, agregue un nuevo campo a la clase isFullScreen dentro de CanvasGrid:
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;
Editar updateView para realizar un seguimiento del estado
Agregue lo siguiente al updateView método para realizar el seguimiento del 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 de isFullScreen a las propiedades de representación 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
);
Resaltando filas
Ya está listo para agregar la funcionalidad de resaltado de fila condicional. Ya ha definido las propiedades de entrada HighlightValue y HighlightColor, y las HighlightIndicatorproperty-set.
property-set permite al creador elegir un campo que se va a usar para comparar con el valor que proporcionan en HighlightValue.
Importación de tipos para admitir el resaltado
La representación de filas personalizada en DetailsList requiere algunas importaciones adicionales. Ya hay algunos tipos de @fluentui/react/lib/DetailsList, por lo que agregue IDetailsListProps, IDetailsRowStyles y DetailsRow a esa instrucció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 const rootContainerStyle bloque:
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;
};
Verá lo siguiente:
- Puede recuperar el valor del campo elegido por el creador mediante el
HighlightIndicatoralias mediante:
item?.getValue('HighlightIndicator'). - Cuando el valor del
HighlightIndicatorcampo coincide con el valor de lahighlightValuepropiedad de entrada proporcionada en el componente de código, puede agregar un color de fondo a la fila. -
DetailsRowutiliza el componenteDetailsListpara renderizar las columnas que usted definió. No es necesario cambiar el comportamiento distinto del color de fondo.
Agregue accesorios adicionales para apoyar el resaltado
Agregue algunos props adicionales para highlightColor y highlightValue que serán proporcionados por la representación dentro de updateView. Ya ha agregado a la GridProps interfaz, por lo que solo tiene que agregarlos a la estructuració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 onRenderRow método a las DetailsList propiedades:
<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 del entorno de Dataverse, asegúrese de que hay un publicador 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 que aparece a continuación. Más información: Creación de un publicador 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.comReemplace
myorg.crm.dynamics.compor la dirección URL de su propio entorno de Dataverse. Inicie sesión con un usuario administrador o 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 el componente de código, use:
pac pcf push --publisher-prefix samplesNota:
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 Build Tools for Visual Studio 2019, asegurándose de seleccionar la carga de trabajo "herramientas de compilación .NET", tal como se describe en los requisitos previos.Una vez completado, este proceso habrá creado una solución temporal pequeña denominada PowerAppTools_samples en el entorno y el
CanvasGridcomponente de código 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: Administración del ciclo de vida de las aplicaciones (ALM) de componentes de código.
** Para usar componentes de código dentro de aplicaciones de tipo canvas, debe habilitar el framework de componentes Power Apps para aplicaciones de tipo canvas en el entorno que está utilizando.
a) Abra el Centro de administración (admin.powerplatform.microsoft.com) y vaya al entorno. b. Vaya a Configuración>Características> . Asegúrese de que Power Apps Component Framework para aplicaciones de lienzo está activado:
Cree una nueva aplicación de lienzo utilizando el diseño Tablet.
En el panel Insertar , seleccione Obtener más componentes.
Seleccione la pestaña Código en el panel Importar componentes .
Seleccione el
CanvasGridcomponente.Seleccione Importar. El componente de código aparecerá ahora en Componentes de código en el panel Insertar .
Arrastre el componente
CanvasGrida la pantalla y enlácelo a la tablaContactsen Microsoft Dataverse.Establezca las siguientes propiedades en el
CanvasGridcomponente de código mediante el panel de propiedades:- Valor =
1: este es el valor questatecodetiene cuando el registro está inactivo. - Color =
#FDE7E9: este es el color que se va a usar cuando el registro está inactivo. -
HighlightIndicator="statecode": este es el campo con el que se va a comparar. Esto estará en el panel Avanzado de la sección DATA .
- Valor =
Agregue un nuevo
TextInputcomponente y asígneletxtSearchel nombre .Actualice la
CanvasGrid.Itemspropiedad para que seaSearch(Contacts,txtSearch.Text,"fullname").Cuando escriba la entrada de texto, verá que los contactos se filtran en la cuadrícula.
Agregue una nueva etiqueta Text y establezca el texto como "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 como
CanvasGrid1.FilteredRecordCount=0.
Esto significa que cuando no hay registros que coincidan con el txtSearch valor o si se aplica un filtro de columna mediante el menú contextual que no devuelve ningún registro (por ejemplo, Nombre completo no contiene datos), se mostrará la etiqueta.
Agregue un formulario para mostrar (desde el grupo Entrada en el panel Insertar ).
Establezca el formulario
DataSourceen laContactstabla y agregue algunos campos de formulario.Establezca la propiedad form
ItemenCanvasGrid1.Selected.Ahora debería ver que al seleccionar elementos en la cuadrícula, el formulario muestra el elemento seleccionado.
Agregue una nueva pantalla a la aplicación de lienzo llamada
scrDetails.Copie el formulario de la pantalla anterior y péguelo en la nueva pantalla.
Establezca la
CanvasGrid1.OnSelectpropiedad comoNavigate(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 el componente de código mientras se ejecuta dentro de la Canvas App abriendo las Herramientas de Desarrollo mediante Ctrl+Shift+I.
Seleccione Ctrl+P y escriba Grid.tsx o Index.ts. A continuación, puede establecer un punto de interrupción y ejecutar el código paso a paso.
Si necesita realizar más cambios en el componente, no es necesario implementarlos cada vez. En su lugar, use la técnica descrita en Depurar componentes de código para crear un AutoResponder en Fiddler para cargar el archivo desde el sistema de archivos local mientras npm start watch está en ejecución.
AutoResponder tendría un aspecto similar al siguiente:
REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.CanvasGrid[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\CanvasGrid\out\controls\CanvasGrid\${folder}\${fname}
También deberá habilitar los filtros para agregar el Access-Control-Allow-Origin encabezado. Más información: Debugging después de realizar la implementación en Microsoft Dataverse.
Tendrá que vaciar la memoria caché y actualizar forzadamente en la sesión del navegador para que el archivo AutoResponder sea reconocido. Una vez cargado, simplemente puede actualizar el explorador, 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 los cambios, puede incrementar la versión de revisión en el manifiesto y, a continuación, volver a implementar mediante pac pcf push.
Hasta ahora, ha implementado una compilación de desarrollo, que no está optimizada y se ejecutará más lentamente en tiempo de ejecución. Puede optar por implementar una compilación optimizada usando 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 del marco de componentes de Power Apps
Crear el primer componente
Depurar componentes de código