Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
En este tutorial, vas a crear un componente de código para el conjunto de datos de una aplicación de lienzo, desplegarlo, agregarlo a una pantalla y probar el componente con 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 luego Archivo>Abrir carpeta y seleccione la
CanvasGridcarpeta. Si ha agregado las extensiones del Explorador de Windows durante la instalación de Visual Studio Code, puede usar la opción de menú contextual Abrir con código dentro de la carpeta. También puede abrir cualquier carpeta en Visual Studio Code mediantecode .en el símbolo del sistema cuando el directorio actual está establecido para esa ubicación.Dentro de un nuevo terminal de PowerShell de Visual Studio Code (Terminal>nuevo 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 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 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 de su elección en Marketplace de Visual Studio Code: 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 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>
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 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;
Luego vuelva a ejecutar el comando npm run build.
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 componente Grid.tsx.
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 de Fluent UI 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 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 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 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:
- La
DetailsListde unaStackestá encapsulada porque luego 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 bloque
gridColumnsestá 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 propiedadescolumnsosortingcambien. 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. - Mantiene un estado interno para
isComponentLoadingen 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 quesortedRecordIdsse actualiza y se restablece el estado. 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
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 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: 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 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:
- Llama a React.createElement, pasando la referencia al contenedor DOM que recibió dentro de la función
init. - El componente
Gridse define dentro deGrid.tsxy se importa en la parte superior del archivo. -
allocatedWidthyallocatedHeightse 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 que mostrar cuando la matriz updatedProperties contiene la
datasetcadena. - En el arnés de prueba, la matriz
updatedPropertiesno está poblada, por lo que puede usar la banderaisTestHarnessque pone en la funcióninitpara cortocircuitar la lógica que establecensortedRecordIdyrecords. 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 Entradas de Datos:
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 la herramienta de ejecución de pruebas solo 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
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 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
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
);
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 elemento invocada (por ejemplo, seleccionando un icono de comilla angular), se eleva 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, 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>
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 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
);
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. 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 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. Búsqueda de una extensión de Visual Studio Code: Búsqueda en Visual Studio Marketplace para un editor 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 las propiedades input/output y el dataset y asociado property-set. Estos se usarán en Power Apps Studio en la etapa de diseño en función del idioma del navegador 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
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;
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 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.
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 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 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 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 momento, ya no puede probar con el arnés de pruebas porque no proporciona compatibilidad con la ordenació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 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 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 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 renderizará utilizando una Fluent UI IconButton, 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 texto
altpara 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 las propiedades 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 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
);
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 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>
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 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, 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 CanvasGrid dentro de index.ts:
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 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
);
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. -
DetailsListutiliza el componenteDetailsRowpara 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 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 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 realizar 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 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 VSdebe 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 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 lienzo, debes habilitar el marco de componentes de Power Apps para aplicaciones de lienzo en el entorno que estás usando.
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 el marco de componentes de Power Apps para aplicaciones de lienzoesté activado:
Cree una nueva aplicación de lienzo utilizando un diseño Tableta.
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
CanvasGridcomponente a la pantalla y enlace a laContactstabla de 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").A medida que escriba en 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 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 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, 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.
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: Depurar 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 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 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 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 las aplicaciones (ALM) con Microsoft Power Platform
Referencia de la API del framework de componentes de Power Apps
Crear el primer componente
Depurar componentes de código