Comparteix a través de


Tutorial: Creación de un componente conjunto de datos de la aplicación de lienzo

En este tutorial, creará un componente de código de conjunto de datos en la aplicación de lienzo, lo implementará, lo agregará a una pantalla y probará el componente usando Visual Studio Code. El componente de código muestra una cuadrícula conjunto de datos desplazable y paginada que proporciona columnas que se pueden ordenar y filtrar. También permite resaltar filas específicas configurando una columna indicadora. Esta es una solicitud común de los creadores de aplicaciones y puede ser compleja de implementar utilizando componentes de aplicaciones de lienzo nativos. Los componentes de código se pueden escribir para que funcionen tanto en aplicaciones de lienzo como las basadas en modelos. Sin embargo, este componente está escrito para su uso específico dentro de las aplicaciones de lienzo.

Además de estos requisitos, también se asegurará de que el componente de código siga la guía de prácticas recomendadas:

  1. Uso de Interfaz de usuario fluida de Microsoft
  2. Localización de las etiquetas de los componentes de código tanto en el diseño como en el runtime
  3. 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
  4. Consideración para el creador de la aplicación para personalizar la interfaz de usuario utilizando propiedades de entrada y elementos externos de la aplicación en la medida de lo posible

Demostración de cuadrícula de lienzo

Nota

Antes de comenzar, asegúrese de haber instalado todos los componentes de los requisitos previos.

Código

Puede descargar el ejemplo completo en PowerApps-Samples/component-framework/CanvasGridControl/.

Crea un nuevo proyecto pcfproj

  1. Cree una nueva carpeta para usar con su componente de código. Por ejemplo, C:\repos\CanvasGrid.

  2. Abra Visual Studio Code y después Archivo > Carpeta abierta y seleccione la carpeta CanvasGrid. Si ha agregado las extensiones del Explorador de Windows durante la instalación de Visual Studio Code, puede utilizar la opción Abrir con Code del menú contextual dentro de la carpeta. También puede cargar cualquier carpeta en Visual Studio Code usando code . en el símbolo del sistema cuando el directorio actual se establece en esa ubicación.

  3. Dentro de una terminal de PowerShell Visual Studio Code (Terminal > Nueva terminal), use el comando pac pcf init para crear un nuevo proyecto de componente de código:

    pac pcf init --namespace SampleNamespace --name CanvasGrid --template dataset
    

    o usando la forma corta:

    pac pcf init -ns SampleNamespace -n CanvasGrid -t dataset
    
  4. Esto agrega un nuevo pcfproj y archivos relacionados a la carpeta actual, incluyendo un packages.json que define los módulos necesarios. Para instalar los módulos obligatorios, use npm install:

    npm install
    

    Nota

    Si recibe el mensaje The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program., asegúrese de haber instalado todos los requisitos previos, específicamente node.js (Se recomienda la versión LTS).

    Cuadrícula de lienzo conjunto de datos

La plantilla incluye un archivo index.ts junto con varios archivos de configuración. Este es el punto de partida de su componente de código y contiene los métodos de ciclo de vida descritos en Implementación de componentes.

Instalar Interfaz de usuario fluida de Microsoft

Utilizará Microsoft Fluent UI y React para crear interfaces de usuario, por lo que debe instalarlos como dependencias. Utilice lo siguiente en la terminal:

npm install react react-dom @fluentui/react

Esto agrega los módulos a packages.json y los instala en la carpeta node_modules. No comprometerá node_modules en el control de fuente, ya que todos los módulos requeridos se pueden restaurar usando npm install.

Una de las ventajas de Microsoft Fluent UI es que proporciona una IU coherente y muy accesible.

Configurando eslint

La plantilla utilizada por pac pcf init instala el módulo eslint a su proyecto y lo configura agregando un archivo .eslintrc.json. Eslint ahora requiere la configuración para los estilos de codificación TypeScript y React. Más información: Linting: mejores prácticas y orientación para componentes de código.

Definir las propiedades conjunto de datos

El archivo CanvasGrid\ControlManifest.Input.xml define los metadatos que describen el comportamiento del componente de código. El atributo control ya tiene el espacio de nombres y el nombre del componente.

Sugerencia

Puede encontrar el XML más fácil de leer si lo formatea para que los atributos aparezcan en líneas separadas. Busque e instale una herramienta de formato XML de su elección en Visual Studio Code Marketplace: Buscar extensiones de formato xml.

Los ejemplos a continuación se han formateado con atributos en líneas separadas para que sean más fáciles de leer.

Debe definir los registros a los que se puede vincular el componente de código, agregando lo siguiente dentro del elemento control, reemplazando el elemento data-set existente:

<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 para que se utilice como indicador de resaltado de fila.

Sugerencia

Puede especificar varios elementos del conjunto de datos. Esto podría ser útil si desea buscar un conjunto de datos pero mostrar una lista de registros usando un segundo.

Definición de las propiedades de entrada y salida

Además de conjunto de datos, puede proporcionar las siguientes propiedades de entrada:

  • HighlightValue- Permite que el creador de la aplicación proporcione un valor que se comparará con la columna definida como HighlightIndicator property-set. Cuando los valores son iguales, la fila debe resaltarse.
  • HighlightColor: permite que el creador de aplicaciones seleccione un color para resaltar las filas.

Sugerencia

Se recomienda proporcionar propiedades de entrada para diseñar aspectos comunes de los componentes de su código al crear componentes de código para usar en aplicaciones de lienzo.

Además de las propiedades de entrada, una propiedad de salida nombrada FilteredRecordCount se actualizará (y activa el evento OnChange) cuando el recuento de filas cambia debido a una acción de filtro aplicada dentro del componente de código. Esto es útil cuando desea mostrar un mensaje No Rows Found dentro de la aplicación principal.

Nota

En el futuro, los componentes de código admitirán eventos personalizados para que pueda definir un evento específico en lugar de utilizar el evento genérico OnChange.

Para definir estas tres propiedades, agregue lo siguiente al archivo CanvasGrid\ControlManifest.Input.xml, debajo del elemento data-set:

<property name="FilteredRecordCount"
  display-name-key="FilteredRecordCount_Disp"
  description-key="FilteredRecordCount_Desc"
  of-type="Whole.None"
  usage="output" />
<property name="HighlightValue"
  display-name-key="HighlightValue_Disp"
  description-key="HighlightValue_Desc"
  of-type="SingleLine.Text"
  usage="input"
  required="true"/>
<property name="HighlightColor"
  display-name-key="HighlightColor_Disp"
  description-key="HighlightColor_Desc"
  of-type="SingleLine.Text"
  usage="input"
  required="true"/>

Guarde este archivo, y luego en la línea de comandos, use:

npm run build

Nota

Si recibe un error como este mientras ejecuta npm run build:

[2:48:57 PM] [build]  Running ESLint...
[2:48:57 PM] [build]  Failed:
[pcf-1065] [Error] ESLint validation error:
C:\repos\CanvasGrid\CanvasGrid\index.ts
  2:47  error  'PropertyHelper' is not defined  no-undef

Abra el archivo index.ts y agregue esto: // eslint-disable-next-line no-undef, directamente encima de la línea:
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;

Luego vuelva a ejecutar el comando npm run build.

Una vez creado el componente, verá que:

  • Un archivo generado automáticamente CanvasGrid\generated\ManifestTypes.d.ts se agrega a su proyecto. Esto se genera como parte del proceso de construcción desde ControlManifest.Input.xml y proporciona los tipos para interactuar con las propiedades de entrada / salida.

  • La salida de la compilación se agrega a la carpeta out. bundle.js es el JavaScript transpilado que se ejecuta dentro del navegador, y ControlManifest.xml es una versión reformateada del archivo ControlManifest.Input.xml que se utiliza durante la implementación.

    Nota

    No modifique el contenido de las carpetas generated y out directamente. Se sobrescribirán como parte del proceso de compilación.

Agregar el componente Grid Fluent UI React

Cuando el componente de código usa React, debe haber un solo componente raíz que se representa dentro del método updateView. Dentro de la carpeta CanvasGrid, agregue un nuevo archivo TypeScript llamado Grid.tsx y agregue el siguiente contenido:

import {
    DetailsList,
    ConstrainMode,
    DetailsListLayoutMode,
    IColumn,
    IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Overlay } from '@fluentui/react/lib/Overlay';
import { 
   ScrollablePane, 
   ScrollbarVisibility 
} from '@fluentui/react/lib/ScrollablePane';
import { Stack } from '@fluentui/react/lib/Stack';
import { Sticky } from '@fluentui/react/lib/Sticky';
import { StickyPositionType } from '@fluentui/react/lib/Sticky';
import { IObjectWithKey } from '@fluentui/react/lib/Selection';
import { IRenderFunction } from '@fluentui/react/lib/Utilities';
import * as React from 'react';

type DataSet = ComponentFramework.PropertyHelper.DataSetApi.EntityRecord & IObjectWithKey;

export interface GridProps {
    width?: number;
    height?: number;
    columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
    records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
    sortedRecordIds: string[];
    hasNextPage: boolean;
    hasPreviousPage: boolean;
    totalResultCount: number;
    currentPage: number;
    sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
    filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
    resources: ComponentFramework.Resources;
    itemsLoading: boolean;
    highlightValue: string | null;
    highlightColor: string | null;
}

const onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (props, defaultRender) => {
    if (props && defaultRender) {
        return (
            <Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>
                {defaultRender({
                    ...props,
                })}
            </Sticky>
        );
    }
    return null;
};

const onRenderItemColumn = (
    item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord,
    index?: number,
    column?: IColumn,
) => {
    if (column && column.fieldName && item) {
        return <>{item?.getFormattedValue(column.fieldName)}</>;
    }
    return <></>;
};

export const Grid = React.memo((props: GridProps) => {
    const {
        records,
        sortedRecordIds,
        columns,
        width,
        height,
        hasNextPage,
        hasPreviousPage,
        sorting,
        filtering,
        currentPage,
        itemsLoading,
    } = props;

    const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);

    const items: (DataSet | undefined)[] = React.useMemo(() => {
        setIsLoading(false);

        const sortedRecords: (DataSet | undefined)[] = sortedRecordIds.map((id) => {
            const record = records[id];
            return record;
        });

        return sortedRecords;
    }, [records, sortedRecordIds, hasNextPage, setIsLoading]);

    const gridColumns = React.useMemo(() => {
        return columns
            .filter((col) => !col.isHidden && col.order >= 0)
            .sort((a, b) => a.order - b.order)
            .map((col) => {
                const sortOn = sorting && sorting.find((s) => s.name === col.name);
                const filtered =
                    filtering && 
                    filtering.conditions && 
                    filtering.conditions.find((f) => f.attributeName == col.name);
                return {
                    key: col.name,
                    name: col.displayName,
                    fieldName: col.name,
                    isSorted: sortOn != null,
                    isSortedDescending: sortOn?.sortDirection === 1,
                    isResizable: true,
                    isFiltered: filtered != null,
                    data: col,
                } as IColumn;
            });
    }, [columns, sorting]);

    const rootContainerStyle: React.CSSProperties = React.useMemo(() => {
        return {
            height: height,
            width: width,
        };
    }, [width, height]);

    return (
        <Stack verticalFill grow style={rootContainerStyle}>
            <Stack.Item grow style={{ position: 'relative', backgroundColor: 'white' }}>
                <ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
                    <DetailsList
                        columns={gridColumns}
                        onRenderItemColumn={onRenderItemColumn}
                        onRenderDetailsHeader={onRenderDetailsHeader}
                        items={items}
                        setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
                        initialFocusedIndex={0}
                        checkButtonAriaLabel="select row"
                        layoutMode={DetailsListLayoutMode.fixedColumns}
                        constrainMode={ConstrainMode.unconstrained}
                    ></DetailsList>
                </ScrollablePane>
                {(itemsLoading || isComponentLoading) && <Overlay />}
            </Stack.Item>
        </Stack>
    );
});

Grid.displayName = 'Grid';

Nota

El archivo tiene la extensión tsx que es un archivo TypeScript que admite la sintaxis de estilo XML utilizada por React. Se compila en JavaScript estándar mediante el proceso de compilación.

Notas de diseño de Grid

Esta sección incluye aspectos comunes en el diseño del componente Grid.tsx.

Es un componente funcional

Este es un componente funcional de React, pero igualmente podría ser un componente de clase. Esto se basa en su estilo de codificación preferido. Los componentes de clase y los componentes funcionales también se pueden mezclar en el mismo proyecto. Tanto los componentes de función como de clase utilizan la sintaxis de estilo tsx XML utilizada por React. Más información: Componentes funcionales y de clase

Minimizar el tamaño de bundle.js

Al importar los componentes de la UI de Fluent ChoiceGroup utilizando importaciones basadas en rutas, en lugar de:

import { 
    DetailsList, 
    ConstrainMode, 
    DetailsListLayoutMode, 
    IColumn, 
    IDetailsHeaderProps, 
    Stack 
} from "@fluentui/react";

Este código usa:

import {
    DetailsList,
    ConstrainMode,
    DetailsListLayoutMode,
    IColumn,
    IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Stack } from '@fluentui/react/lib/Stack';

De esta forma, el tamaño del paquete será más pequeño, lo que dará como resultado requisitos de capacidad más bajos y un mejor rendimiento en tiempo de ejecución.

Una alternativa sería utilizar temblor de árboles.

Asignación de desestructuración

Este código:

export const Grid = React.memo((props: GridProps) => {
    const {
        records,
        sortedRecordIds,
        columns,
        width,
        height,
        hasNextPage,
        hasPreviousPage,
        sorting,
        filtering,
        currentPage,
        itemsLoading,
    } = props;

Usa Asignación de desestructuración. De esta forma, extrae los atributos requeridos para representar a partir de las propiedades, en lugar de prefijarlos con props. cada vez que se utilizan.

El código también usa React.memo para encapsular el componente funcional, para que no se represente a menos que alguna de las propiedades de entrada haya cambiado.

Uso de React.useMemo

React.useMemo se utiliza en varios lugares para garantizar que la matriz de elementos creada solo se mute cuando los accesorios de entrada options o configuration cambian. Ésta es una práctica recomendada de los componentes de función que reduce las representaciones innecesarias de los componentes secundarios.

Otros elementos a tener en cuenta:

  • La DetailsList de una Stack está encapsulada porque luego se agregará un elemento de pie de página con los controles de paginación.
  • El componente de la interfaz de usuario Fluent Sticky se usa para encapsular las columnas de encabezado (usando onRenderDetailsHeader) para que permanezcan visibles al desplazarse por la cuadrícula.
  • setKey se pasa a DetailsList junto con initialFocusedIndex de modo que cuando cambie la página actual, se restablecerán la posición de desplazamiento y la selección.
  • La función onRenderItemColumn se utiliza para representar el contenido de la celda. Acepta el elemento de la fila y usa getFormattedValue para devolver el valor de visualización de la columna. El método getValue devuelve un valor que podría utilizar para proporcionar una representación alternativa. La ventaja de getFormattedValue es que contiene una cadena formateada para columnas de tipos que no son cadenas, como fechas y búsquedas.
  • El bloque gridColumns está asignando la forma del objeto de las columnas proporcionadas por el contexto conjunto de datos, sobre la forma esperada por la propiedad de columnas DetailsList. Dado que esto está encapsulado en el enlace de React.useMemo, la salida solo cambiará cuando las propiedades columns o sorting cambien. Puede mostrar los iconos de clasificación y filtrado en las columnas donde los detalles de clasificación y filtrado proporcionados por el contexto del componente de código coinciden con la columna que se está mapeando. Las columnas se ordenan utilizando la propiedad column.order para asegurarse de que estén en el orden correcto en la cuadrícula, según lo definido por el fabricante de la aplicación.
  • Mantiene un estado interno para isComponentLoading en nuestro componente React. Esto se debe a que cuando el usuario selecciona acciones de clasificación y filtrado, puede atenuar la cuadrícula como una señal visual hasta que sortedRecordIds se actualiza y se restablece el estado. Hay una propiedad de entrada adicional llamada itemsLoading que se asigna a la propiedad dataset.loading proporcionada por el contexto de conjunto de datos. Ambos indicadores se utilizan para controlar la señal de carga visual que se implementa mediante el componente Overlay de la interfaz de usuario Fluent.

Actualizar index.ts

El siguiente paso es realizar cambios en el archivo index.ts para que coincida con las propiedades definidas en Grid.tsx.

Agregar instrucciones de importación e inicializar iconos

Reemplace las importaciones existentes con lo siguiente en el encabezado de index.ts:

import {IInputs, IOutputs} from './generated/ManifestTypes';
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;

Nota

La importación de initializeIcons es obligatoria porque este código usa el conjunto de iconos Fluent UI. Llame a initializeIcons para cargar los iconos dentro del arnés de prueba. Dentro de las aplicaciones de lienzo, ya están inicializadas.

Agregar campos a la clase CanvasGrid

Agregue los siguientes campos a la clase CanvasGrid:

export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {

    /**
     * Empty constructor.
     */
    constructor() {

    }

Actualizar el método init

Agregue lo siguiente a init:

public init(
    context: ComponentFramework.Context<IInputs>, 
    notifyOutputChanged: () => void, 
    state: ComponentFramework.Dictionary, 
    container: HTMLDivElement): void {
    // Add control initialization code
}

La función init se llama cuando el componente de código se inicializa por primera vez en la pantalla de una aplicación. Almacena una referencia a lo siguiente:

  • notifyOutputChanged: Esta es la devolución de llamada siempre que llame para notificar a la aplicación de lienzo que una de las propiedades ha cambiado.
  • container: este es el elemento DOM al que se agrega la interfaz de usuario del componente de código.
  • resources: se utiliza para recuperar cadenas localizadas en el idioma del usuario actual.

context.mode.trackContainerResize(true)) se usa para que updateView se llame cuando el componente de código cambie de tamaño.

Nota

Actualmente no hay forma de determinar si el componente de código se está ejecutando dentro del arnés de prueba. Debe detectar si el elemento div de control-dimensions está presente como indicador.

Actualice el método updateView

Agregue lo siguiente a updateView:

public updateView(context: ComponentFramework.Context<IInputs>): void {
    // Add code to update control view
}

Puede ver que:

  • Llama a React.createElement, pasando la referencia al contenedor DOM que recibió dentro de la función init.
  • El componente Grid se define dentro de Grid.tsx y se importa en la parte superior del archivo.
  • allocatedWidth y allocatedHeight se proporcionan mediante el contexto principal siempre que cambien (por ejemplo, la aplicación cambia el tamaño del componente de código o usted entra en el modo de pantalla completa) desde que realizó una llamada a trackContainerResize(true) dentro de la función init.
  • Puede detectar cuándo hay nuevas filas para mostrar cuando el la matriz updatedProperties contiene la cadena dataset.
  • En el arnés de prueba, la matriz updatedProperties no está poblada, por lo que puede usar la bandera isTestHarness que pone en la función init para cortocircuitar la lógica que establecen sortedRecordId y records. Mantiene una referencia a los valores actuales hasta que cambian, de modo que no los mute cuando se pasa al componente secundario a menos que se requiera una nueva representación de los datos.
  • Dado que el componente de código mantiene el estado de la página que se muestra el número de página se restablece cuando el contexto principal restablece los registros a la primera página. Puede saber que está de vuelta a la primera página cuando hasPreviousPage es falso.

Actualice el método destroy

Por último, debe ordenar cuando se destruye el componente de código:

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 estén guardados y en el terminal use:

npm start watch

Debe establecer el ancho y el alto para ver la cuadrícula del componente de código que se completa con los tres registros de muestra. Luego puede exportar un conjunto de registros a un archivo CSV desde Dataverse y luego cargarlo en la herramienta de pruebas usando Entradas de datos > Panel de registros:

Agente de prueba

Aquí hay algunos datos de muestra separados por comas que puede guardar en un archivo .csv y usar:

address1_city,address1_country,address1_stateorprovince,address1_line1,address1_postalcode,telephone1,emailaddress1,firstname,fullname,jobtitle,lastname
Seattle,U.S.,WA,7842 Ygnacio Valley Road,12150,555-0112,someone_m@example.com,Thomas,Thomas Andersen (sample),Purchasing Manager,Andersen (sample)
Renton,U.S.,WA,7165 Brock Lane,61795,555-0109,someone_j@example.com,Jim,Jim Glynn (sample),Owner,Glynn (sample)
Snohomish,U.S.,WA,7230 Berrellesa Street,78800,555-0106,someone_g@example.com,Robert,Robert Lyon (sample),Owner,Lyon (sample)
Seattle,U.S.,WA,931 Corte De Luna,79465,555-0111,someone_l@example.com,Susan,Susan Burk (sample),Owner,Burk (sample)
Seattle,U.S.,WA,7765 Sunsine Drive,11910,555-0110,someone_k@example.com,Patrick,Patrick Sands (sample),Owner,Sands (sample)
Seattle,U.S.,WA,4948 West Th St,73683,555-0108,someone_i@example.com,Rene,Rene Valdes (sample),Purchasing Assistant,Valdes (sample)
Redmond,U.S.,WA,7723 Firestone Drive,32147,555-0107,someone_h@example.com,Paul,Paul Cannon (sample),Purchasing Assistant,Cannon (sample)
Issaquah,U.S.,WA,989 Caravelle Ct,33597,555-0105,someone_f@example.com,Scott,Scott Konersmann (sample),Purchasing Manager,Konersmann (sample)
Issaquah,U.S.,WA,7691 Benedict Ct.,57065,555-0104,someone_e@example.com,Sidney,Sidney Higa (sample),Owner,Higa (sample)
Monroe,U.S.,WA,3747 Likins Avenue,37925,555-0103,someone_d@example.com,Maria,Maria Campbell (sample),Purchasing Manager,Campbell (sample)
Duvall,U.S.,WA,5086 Nottingham Place,16982,555-0102,someone_c@example.com,Nancy,Nancy Anderson (sample),Purchasing Assistant,Anderson (sample)
Issaquah,U.S.,WA,5979 El Pueblo,23382,555-0101,someone_b@example.com,Susanna,Susanna Stubberod (sample),Purchasing Manager,Stubberod (sample)
Redmond,U.S.,WA,249 Alexander Pl.,86372,555-0100,someone_a@example.com,Yvonne,Yvonne McKay (sample),Purchasing Manager,McKay (sample)

Nota

Solo se muestra una columna en la herramienta de ejecución de pruebas, independientemente de las columnas que proporcione en el archivo CSV cargado. Esto se debe a que la herramienta de ejecución de pruebas solo muestra property-set cuando hay uno definido. Si no se define property-set, las columnas se rellenarán con todas las columnas del archivo CSV cargado.

Agregar selección de fila

Aunque la interfaz de usuario Fluent DetailsList permite seleccionar registros por defecto, los registros seleccionados no están vinculados a la salida del componente de código. Necesita las propiedades Selected y SelectedItems para reflejar los registros elegidos dentro de una aplicación de lienzo, de modo que los componentes relacionados se puedan actualizar. En este ejemplo, permite la selección de un solo elemento a la vez, por lo que SelectedItems solo contendrá un solo registro.

Actualizar las importaciones de Grid.tsx

Añada lo siguiente a las importaciones en Grid.tsx:

import {
    DetailsList,
    ConstrainMode,
    DetailsListLayoutMode,
    IColumn,
    IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Overlay } from '@fluentui/react/lib/Overlay';
import { 
   ScrollablePane, 
   ScrollbarVisibility 
} from '@fluentui/react/lib/ScrollablePane';
import { Stack } from '@fluentui/react/lib/Stack';
import { Sticky } from '@fluentui/react/lib/Sticky';
import { StickyPositionType } from '@fluentui/react/lib/Sticky';
import { IObjectWithKey } from '@fluentui/react/lib/Selection';
import { IRenderFunction } from '@fluentui/react/lib/Utilities';
import * as React from 'react';

Agregar setSelectedRecords a GridProps

A la interfaz GridProps, dentro de Grid.tsx, agregue lo siguiente:

export interface GridProps {
    width?: number;
    height?: number;
    columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
    records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
    sortedRecordIds: string[];
    hasNextPage: boolean;
    hasPreviousPage: boolean;
    totalResultCount: number;
    currentPage: number;
    sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
    filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
    resources: ComponentFramework.Resources;
    itemsLoading: boolean;
    highlightValue: string | null;
    highlightColor: string | null;
}

Agregar la propiedad setSelectedRecords a Grid

Dentro del componente de función Grid.tsx, actualice la desestructuración de props para agregar el nuevo prop setSelectedRecords.

export const Grid = React.memo((props: GridProps) => {
    const {
        records,
        sortedRecordIds,
        columns,
        width,
        height,
        hasNextPage,
        hasPreviousPage,
        sorting,
        filtering,
        currentPage,
        itemsLoading,
    } = props;

Directamente debajo, agregue:

const forceUpdate = useForceUpdate();
const onSelectionChanged = React.useCallback(() => {
  const items = selection.getItems() as DataSet[];
  const selected = selection.getSelectedIndices().map((index: number) => {
    const item: DataSet | undefined = items[index];
    return item && items[index].getRecordId();
  });

  setSelectedRecords(selected);
  forceUpdate();
}, [forceUpdate]);

const selection: Selection = useConst(() => {
  return new Selection({
    selectionMode: SelectionMode.single,
    onSelectionChanged: onSelectionChanged,
  });
});

Los enlaces React.useCallback y useConst garantizan que estos valores no cambien entre las representaciones y provoquen una representación innecesaria de componentes secundarios.

El enlace useForceUpdate garantiza que cuando se actualiza la selección, el componente se vuelve a representar para reflejar el recuento de selección actualizado.

Agregar selección a DetailsList

El objeto selection creado para mantener el estado de la selección se pasa luego al componente DetailsList:

<DetailsList
   columns={gridColumns}
   onRenderItemColumn={onRenderItemColumn}
   onRenderDetailsHeader={onRenderDetailsHeader}
   items={items}
   setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
   initialFocusedIndex={0}
   checkButtonAriaLabel="select row"
   layoutMode={DetailsListLayoutMode.fixedColumns}
   constrainMode={ConstrainMode.unconstrained}
></DetailsList>

Definir la devolución de llamada setSelectedRecords

Necesita definir la nueva devolución de llamada setSelectedRecords dentro de index.ts y páselo al componente Grid. Cerca de la parte superior de la clase CanvasGrid, agregue lo siguiente:

export class CanvasGrid
  implements ComponentFramework.StandardControl<IInputs, IOutputs>
{
  notifyOutputChanged: () => void;
  container: HTMLDivElement;
  context: ComponentFramework.Context<IInputs>;
  sortedRecordsIds: string[] = [];
  resources: ComponentFramework.Resources;
  isTestHarness: boolean;
  records: {
    [id: string]: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord;
  };
  currentPage = 1;
  filteredRecordCount?: number;

Nota

El método se define como una función de flecha para vincularlo a instancia this actual del componente de código.

La llamada a setSelectedRecordIds informa a la aplicación de lienzo que la selección ha cambiado para que otros componentes que hacen referencia SelectedItems y Selected se actualicen.

Agregue una nueva devolución de llamada a las propiedades de input

Finalmente, agregue la nueva devolución de llamada a las propiedades de entrada del componente Grid en el método updateView:

ReactDOM.render(
 React.createElement(Grid, {
   width: allocatedWidth,
   height: allocatedHeight,
   columns: dataset.columns,
   records: this.records,
   sortedRecordIds: this.sortedRecordsIds,
   hasNextPage: paging.hasNextPage,
   hasPreviousPage: paging.hasPreviousPage,
   currentPage: this.currentPage,
   totalResultCount: paging.totalResultCount,
   sorting: dataset.sorting,
   filtering: dataset.filtering && dataset.filtering.getFilter(),
   resources: this.resources,
   itemsLoading: dataset.loading,
   highlightValue: this.context.parameters.HighlightValue.raw,
   highlightColor: this.context.parameters.HighlightColor.raw,
 }),
this.container
);

Invocando el evento OnSelect

Hay un patrón en las aplicaciones de lienzo en el que, si una galería o cuadrícula tiene una selección de elemento invocada (por ejemplo, seleccionando un icono de comilla angular), se eleva el evento OnSelect. Puede implementar este patrón usando el método openDatasetItem del conjunto de datos.

Añadir onNavigate a la interfaz de GridProps

Como antes, agrega un accesorio de devolución de llamada adicional en el componente Grid agregando lo siguiente a la interfaz GridProps en Grid.tsx:

export interface GridProps {
  width?: number;
  height?: number;
  columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
  records: Record<
    string,
    ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
  >;
  sortedRecordIds: string[];
  hasNextPage: boolean;
  hasPreviousPage: boolean;
  totalResultCount: number;
  currentPage: number;
  sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
  filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
  resources: ComponentFramework.Resources;
  itemsLoading: boolean;
  highlightValue: string | null;
  highlightColor: string | null;
  setSelectedRecords: (ids: string[]) => void;
}

Agregar onNavigate a los accesorios de Grid

Nuevamente, debe agregar el nuevo accesorio a la desestructuración de los accesorios:

export const Grid = React.memo((props: GridProps) => {
  const {
    records,
    sortedRecordIds,
    columns,
    width,
    height,
    hasNextPage,
    hasPreviousPage,
    sorting,
    filtering,
    currentPage,
    itemsLoading,
    setSelectedRecords,
  } = props;

Agregar onItemInvoked a DetailsList

DetailList tiene un accesorio de devolución de llamada llamado onItemInvoked que, a su vez, pasa su devolución de llamada a:

<DetailsList
   columns={gridColumns}
   onRenderItemColumn={onRenderItemColumn}
   onRenderDetailsHeader={onRenderDetailsHeader}
   items={items}
   setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
   initialFocusedIndex={0}
   checkButtonAriaLabel="select row"
   layoutMode={DetailsListLayoutMode.fixedColumns}
   constrainMode={ConstrainMode.unconstrained}
   selection={selection}
></DetailsList>

Agregar el método onNavigate a index.ts

Añade el método onNavigate para el index.ts justo debajo del método setSelectedRecords:

onNavigate = (
  item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
): void => {
  if (item) {
    this.context.parameters.records.openDatasetItem(item.getNamedReference());
  }
};

Esto simplemente invoca el método openDatasetItem en el registro conjunto de datos para que el componente de código aumente el evento OnSelect. El método se define como una función de flecha para vincularlo a instancia this actual del componente de código.

Debe pasar esta devolución de llamada a accesorios de componentes Grid dentro del método updateView:

    ReactDOM.render(
      React.createElement(Grid, {
        width: allocatedWidth,
        height: allocatedHeight,
        columns: dataset.columns,
        records: this.records,
        sortedRecordIds: this.sortedRecordsIds,
        hasNextPage: paging.hasNextPage,
        hasPreviousPage: paging.hasPreviousPage,
        currentPage: this.currentPage,
        totalResultCount: paging.totalResultCount,
        sorting: dataset.sorting,
        filtering: dataset.filtering && dataset.filtering.getFilter(),
        resources: this.resources,
        itemsLoading: dataset.loading,
        highlightValue: this.context.parameters.HighlightValue.raw,
        highlightColor: this.context.parameters.HighlightColor.raw,
        setSelectedRecords: this.setSelectedRecords,
      }),
      this.container
    );

Cuando guarde todos los archivos, la herramienta de ejecución de pruebas se recargará. Use Ctrl + Shift + I (o F12) y usa Abrir archivo (Ctrl + P) buscando index.ts y puede colocar un punto de interrupción dentro del método onNavigate. Hacer doble clic en una fila (o resaltarla con las teclas del cursor y pulsar Enter) hará que se alcance el punto de interrupción porque DetailsList invoca la devolución de llamada onNavigate.

Depuración de Canvas Data Grid OnNavigate en index.ts

Hay una referencia a _this porque la función se define como una función de flecha y se ha transpilado en un cierre de JavaScript para capturar la instancia de this.

Agregar la localización

Antes de continuar, debe agregar cadenas de recursos al componente de código para que pueda usar cadenas localizadas para mensajes, como paginación, clasificación y filtrado. Agrega un nuevo archivo CanvasGrid\strings\CanvasGrid.1033.resx y usa el editor de recursos de Visual Studio o Visual Studio Code con una extensión para ingresar lo siguiente:

Name valor
Records_Dataset_Display Registros
FilteredRecordCount_Disp Recuento de registros filtrados
FilteredRecordCount_Desc El número de registros después del filtrado
HighlightValue_Disp Resaltar valor
HighlightValue_Desc Se debe resaltar el valor para indicar una fila
HighlightColor_Disp Color de resaltado
HighlightColor_Desc El color para resaltar una fila usando
HighlightIndicator_Disp Resaltar campo indicador
HighlightIndicator_Desc Establezca el nombre del campo para compararlo con el Valor destacado
Label_Grid_Footer Página {0} ({1} seleccionadas)
Label_SortAZ De la A a la Z
Label_SortZA De la Z a la A
Label_DoesNotContainData No contiene datos
Label_ShowFullScreen Mostrar pantalla completa

Sugerencia

No se recomienda editar archivos resx directamente. En su lugar, use el editor de recursos de Visual Studio o una extensión para Visual Studio Code. Encuentre una extensión de Visual Studio Code: Busque Visual Studio Marketplace para un editor resx

Los datos para este archivo también se pueden configurar abriendo el archivo CanvasGrid.1033.resx en el Bloc de notas y copiando el contenido XML a continuación:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0"/>
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string"/>
              <xsd:attribute name="type" type="xsd:string"/>
              <xsd:attribute name="mimetype" type="xsd:string"/>
              <xsd:attribute ref="xml:space"/>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string"/>
              <xsd:attribute name="name" type="xsd:string"/>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
              <xsd:attribute ref="xml:space"/>
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required"/>
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Records_Dataset_Display" xml:space="preserve">
    <value>Records</value>
  </data>
  <data name="FilteredRecordCount_Disp" xml:space="preserve">
    <value>Filtered Record Count</value>
  </data>
  <data name="FilteredRecordCount_Desc" xml:space="preserve">
    <value>The number of records after filtering</value>
  </data>
  <data name="HighlightValue_Disp" xml:space="preserve">
    <value>Highlight Value</value>
  </data>
  <data name="HighlightValue_Desc" xml:space="preserve">
    <value>The value to indicate a row should be highlighted</value>
  </data>
  <data name="HighlightColor_Disp" xml:space="preserve">
    <value>Highlight Color</value>
  </data>
  <data name="HighlightColor_Desc" xml:space="preserve">
    <value>The color to highlight a row using</value>
  </data>
  <data name="HighlightIndicator_Disp" xml:space="preserve">
    <value>Highlight Indicator Field</value>
  </data>
  <data name="HighlightIndicator_Desc" xml:space="preserve">
    <value>Set to the name of the field to compare against the Highlight Value</value>
  </data>
   <data name="Label_Grid_Footer" xml:space="preserve">
    <value>Page {0} ({1} Selected)</value>
  </data>
  <data name="Label_SortAZ" xml:space="preserve">
    <value>A to Z</value>
  </data>
  <data name="Label_SortZA" xml:space="preserve">
    <value>Z to A</value>
  </data>
  <data name="Label_DoesNotContainData" xml:space="preserve">
    <value>Does not contain data</value>
  </data>
  <data name="Label_ShowFullScreen" xml:space="preserve">
    <value>Show Full Screen</value>
  </data>
</root>

Tiene cadenas de recursos para las propiedades input/output y el dataset y asociado property-set. Estos se utilizarán en Power Apps Studio en el momento del diseño según el idioma del navegador del fabricante. También puede agregar cadenas de etiquetas que se pueden recuperar en tiempo de ejecución usando getString. Más información: Implementación del componente de API de localización.

Agregue este nuevo archivo de recursos al archivo ControlManifest.Input.xml dentro del elemento resources:

<resources>
   <code path="index.ts"
      order="1" />
</resources>

Agregar clasificación y filtrado de columnas

Si desea permitir que el usuario ordene y filtre utilizando encabezados de columna de cuadrícula, la interfaz de usuario de Fluent DetailList proporciona una forma sencilla de agregar menús contextuales a los encabezados de las columnas.

Agregar onSort y onFilter a GridProps

Primero, agregue onSort y onFilter a la interfaz GridProps dentro de Grid.tsx para proporcionar funciones de devolución de llamada para ordenar y filtrar:

export interface GridProps {
  width?: number;
  height?: number;
  columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
  records: Record<
    string,
    ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
  >;
  sortedRecordIds: string[];
  hasNextPage: boolean;
  hasPreviousPage: boolean;
  totalResultCount: number;
  currentPage: number;
  sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
  filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
  resources: ComponentFramework.Resources;
  itemsLoading: boolean;
  highlightValue: string | null;
  highlightColor: string | null;
  setSelectedRecords: (ids: string[]) => void;
  onNavigate: (
    item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
  ) => void;
}

Agregar onSort, onFilter y recursos a props

Luego, agregue estas nuevas propiedades junto con la referencia resources (para que pueda recuperar etiquetas localizadas para ordenar y filtrar), a la desestructuración de accesorios:

export const Grid = React.memo((props: GridProps) => {
  const {
    records,
    sortedRecordIds,
    columns,
    width,
    height,
    hasNextPage,
    hasPreviousPage,
    sorting,
    filtering,
    currentPage,
    itemsLoading,
    setSelectedRecords,
    onNavigate,
  } = props;

Importar componentes de ContextualMenu

Necesita agregar algunas importaciones a la parte superior de Grid.tsx para que pueda usar el componente ContextualMenu proporcionado por Fluent UI. Puede utilizar importaciones basadas en rutas para reducir el tamaño del paquete.

import { ContextualMenu, DirectionalHint, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu';

Agregue la funcionalidad de representación del menú contextual

Ahora agregue la funcionalidad de renderizado del menú contextual a Grid.tsx justo debajo de la línea
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);:

const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);

Podrá ver que:

  • El estado contextualMenuProps controla la visibilidad del menú contextual que se representa con el componente ContextualMenu de la interfaz de usuario Fluent.
  • Este código proporciona un filtro simple para mostrar solo valores donde el campo no contiene ningún dato. Puede ampliar esto para proporcionar un filtrado adicional.
  • Este código usa resources.getString para mostrar etiquetas en el menú contextual que se pueden localizar.
  • El enlace React.useCallback, similar a React.useMemo, garantiza que las devoluciones de llamada solo se mutan cuando cambian los valores dependientes. Esto optimiza la representación de componentes secundarios.

Agregue estas nuevas funciones del menú contextual a la selección de columnas y los eventos del menú contextual

Agregue estas nuevas funciones del menú contextual a la selección de columnas y los eventos del menú contextual. Actulice las const gridColumns para agregar las devoluciones de llamada onColumnContextMenu y onColumnClick:

const gridColumns = React.useMemo(() => {
   return columns
     .filter((col) => !col.isHidden && col.order >= 0)
     .sort((a, b) => a.order - b.order)
     .map((col) => {
       const sortOn = sorting && sorting.find((s) => s.name === col.name);
       const filtered =
         filtering &&
         filtering.conditions &&
         filtering.conditions.find((f) => f.attributeName == col.name);
       return {
         key: col.name,
         name: col.displayName,
         fieldName: col.name,
         isSorted: sortOn != null,
         isSortedDescending: sortOn?.sortDirection === 1,
         isResizable: true,
         isFiltered: filtered != null,
         data: col,
       } as IColumn;
     });
 }, [columns, sorting]);

Agregar menú contextual a la salida renderizada

Para que se muestre el menú contextual, debe agregarlo a la salida renderizada. Agregue lo siguiente directamente debajo del componente DetailsList en la salida devuelta:

<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
    <DetailsList
      columns={gridColumns}
      onRenderItemColumn={onRenderItemColumn}
      onRenderDetailsHeader={onRenderDetailsHeader}
      items={items}
      setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
      initialFocusedIndex={0}
      checkButtonAriaLabel="select row"
      layoutMode={DetailsListLayoutMode.fixedColumns}
      constrainMode={ConstrainMode.unconstrained}
      selection={selection}
      onItemInvoked={onNavigate}
    ></DetailsList>
</ScrollablePane>

Agregar funciones onSort y OnFilter

Ahora que ha agregado la interfaz de usuario de clasificación y filtrado, debe agregar las devoluciones de llamada a index.ts, para realizar la clasificación y el filtrado de los registros vinculados al componente de código. Agregue lo siguiente a index.ts justo debajo de la función onNavigate:

onSort = (name: string, desc: boolean): void => {
  const sorting = this.context.parameters.records.sorting;
  while (sorting.length > 0) {
    sorting.pop();
  }
  this.context.parameters.records.sorting.push({
    name: name,
    sortDirection: desc ? 1 : 0,
  });
  this.context.parameters.records.refresh();
};

onFilter = (name: string, filter: boolean): void => {
  const filtering = this.context.parameters.records.filtering;
  if (filter) {
    filtering.setFilter({
      conditions: [
        {
          attributeName: name,
          conditionOperator: 12, // Does not contain Data
        },
      ],
    } as ComponentFramework.PropertyHelper.DataSetApi.FilterExpression);
  } else {
    filtering.clearFilter();
  }
  this.context.parameters.records.refresh();
};

Podrá ver que:

  • La ordenación y el filtro se aplican a la propiedad conjunto de datos mediante las propiedades de clasificación y filtrado.
  • Al modificar las columnas de clasificación, las definiciones de clasificación existentes deben eliminarse utilizando pop en lugar de reemplazar la matriz de clasificación en sí.
  • Debe llamarse a Actualizar después de aplicar la clasificación y el filtrado. Si un filtro y una ordenación se aplican al mismo tiempo, la actualización solo debe invocarse una vez.

Agregue devoluciones de llamada OnSort y OnFilter a la representación de Grid

Por último, puede pasar estas dos devoluciones de llamada a la llamada de representación de Grid:

ReactDOM.render(
    React.createElement(Grid, {
        width: allocatedWidth,
        height: allocatedHeight,
        columns: dataset.columns,
        records: this.records,
        sortedRecordIds: this.sortedRecordsIds,
        hasNextPage: paging.hasNextPage,
        hasPreviousPage: paging.hasPreviousPage,
        currentPage: this.currentPage,
        totalResultCount: paging.totalResultCount,
        sorting: dataset.sorting,
        filtering: dataset.filtering && dataset.filtering.getFilter(),
        resources: this.resources,
        itemsLoading: dataset.loading,
        highlightValue: this.context.parameters.HighlightValue.raw,
        highlightColor: this.context.parameters.HighlightColor.raw,
        setSelectedRecords: this.setSelectedRecords,
        onNavigate: this.onNavigate,
    }),
    this.container
);

Nota

En este punto, ya no puede realizar pruebas con la herramienta de prueba porque no proporciona soporte para la clasificación y el filtrado. Más tarde, puede implementar usando pac pcf push y luego agregar a una aplicación de lienzo para probar. Si lo desea, puede saltar a ese paso para ver cómo se ve el componente de código dentro de las aplicaciones de lienzo.

Actualice la propiedad de salida FilteredRecordCount

Dado que la cuadrícula ahora puede filtrar registros internamente, es importante informar a la aplicación de lienzo cuántos registros se muestran. Esto es para que pueda mostrar un mensaje de tipo 'Sin registros'.

Sugerencia

Puede implementar esto internamente dentro del componente de código pero, sin embargo, se recomienda que la mayor parte de la interfaz de usuario se deje en manos de la aplicación de lienzo, ya que le dará al creador de la aplicación más flexibilidad.

Ya ha definido una propiedad de salida llamada FilteredRecordCount en el ControlManifest.Input.xml. Cuando se realiza el filtrado y se cargan los registros filtrados, la función updateView será llamada con una cadena dataset en la matriz updatedProperties. Si el número de registros ha cambiado, debe llamar a notifyOutputChanged para que la aplicación de lienzo sepa que debe actualizar los controles que utilizan la propiedad FilteredRecordCount. Dentro del método updateView de index.ts, agregue lo siguiente justo encima de ReactDOM.render y debajo de allocatedHeight:

const allocatedHeight = parseInt(
    context.mode.allocatedHeight as unknown as string
);

Agregue FilteredRecordCount a getOutputs

Esto actualiza filteredRecordCount en la clase de componente de código que definió anteriormente cuando es diferente de los nuevos datos recibidos. Después de llamar a notifyOutputChanged, debe asegurarse de que el valor se devuelva cuando se llama a getOutputs, así que actualice el método getOutputs para ser:

public getOutputs(): IOutputs {
    return {};
}

Agregar paginación a la cuadrícula

Para grandes conjuntos de datos, las aplicaciones de lienzo dividirán los registros en varias páginas. Puede agregar un pie de página que muestre los controles de navegación de la página. Cada botón se renderizará utilizando una interfaz de usuario Fluent IconButton, que debe importar.

Agregar IconButton a las importaciones

Añada esto a las importaciones en Grid.tsx:

import { IconButton } from '@fluentui/react/lib/Button';

Agregar la función stringFormat

El siguiente paso agregará capacidades para cargar el formato para la etiqueta del indicador de página desde las cadenas de recursos ("Page {0} ({1} Selected)") y formatea usando una función simple stringFormat. Esta función podría estar igualmente en un archivo separado y compartido entre sus componentes por conveniencia:

En este tutorial, agréguela en la parte superior de Grid.tsx, directamente debajo de type DataSet ....

function stringFormat(template: string, ...args: string[]): string {
  for (const k in args) {
    template = template.replace("{" + k + "}", args[k]);
  }
  return template;
}

Agregar botones de paginación

En Grid.tsx, agregue el siguiente Stack.Item debajo del Stack.Item existente que contiene el ScrollablePane:

return (
  <Stack verticalFill grow style={rootContainerStyle}>
      <Stack.Item grow style={{ position: 'relative', backgroundColor: 'white' }}>
        <ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
            <DetailsList
              columns={gridColumns}
              onRenderItemColumn={onRenderItemColumn}
              onRenderDetailsHeader={onRenderDetailsHeader}
              items={items}
              setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
              initialFocusedIndex={0}
              checkButtonAriaLabel="select row"
              layoutMode={DetailsListLayoutMode.fixedColumns}
              constrainMode={ConstrainMode.unconstrained}
              selection={selection}
              onItemInvoked={onNavigate}
            ></DetailsList>
            {contextualMenuProps && <ContextualMenu {...contextualMenuProps} />}
        </ScrollablePane>
        {(itemsLoading || isComponentLoading) && <Overlay />}
      </Stack.Item>    
  </Stack>
);

Podrá ver que:

  • El Stack asegura que el pie de página se apilará debajo del DetailsList. El atributo grow se utiliza para asegurarse de que la cuadrícula se expanda para llenar el espacio disponible.
  • Carga el formato para la etiqueta del indicador de página desde las cadenas de recursos ("Page {0} ({1} Selected)") y formatea usando la función stringFormat que agregó en el paso anterior.
  • Puede proporcionar texto alt para accesibilidad en la paginación IconButtons.
  • El estilo del pie de página podría aplicarse igualmente mediante un nombre de clase CSS que hace referencia a un archivo CSS agregado al componente de código.

Agregar props de devolución de llamada para admitir la paginación

A continuación, debe agregar las propiedades de devolución de llamada loadFirstPage, loadNextPage y loadPreviousPage que faltan.

A la interfaz GridProps, agregue lo siguiente:

export interface GridProps {
   width?: number;
   height?: number;
   columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
   records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
   sortedRecordIds: string[];
   hasNextPage: boolean;
   hasPreviousPage: boolean;
   totalResultCount: number;
   currentPage: number;
   sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
   filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
   resources: ComponentFramework.Resources;
   itemsLoading: boolean;
   highlightValue: string | null;
   highlightColor: string | null;
   setSelectedRecords: (ids: string[]) => void;
   onNavigate: (item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord) => void;
   onSort: (name: string, desc: boolean) => void;
   onFilter: (name: string, filtered: boolean) => void;
}

Agregar nuevos props de paginación a Grid

Agregue estas nuevas propiedades a la desestructuración de las propiedades:

export const Grid = React.memo((props: GridProps) => {
   const {
      records,
      sortedRecordIds,
      columns,
      width,
      height,
      hasNextPage,
      hasPreviousPage,
      sorting,
      filtering,
      currentPage,
      itemsLoading,
      setSelectedRecords,
      onNavigate,
      onSort,
      onFilter,
      resources,
   } = props;

Agregar devoluciones de llamada a index.ts

Agregue estas devoluciones de llamada a index.ts bajo el método onFilter:

loadFirstPage = (): void => {
  this.currentPage = 1;
  this.context.parameters.records.paging.loadExactPage(1);
};
loadNextPage = (): void => {
  this.currentPage++;
  this.context.parameters.records.paging.loadExactPage(this.currentPage);
};
loadPreviousPage = (): void => {
  this.currentPage--;
  this.context.parameters.records.paging.loadExactPage(this.currentPage);
};

A continuación, actualice la llamada de representación Grid para incluir estas devoluciones de llamada:

ReactDOM.render(
    React.createElement(Grid, {
        width: allocatedWidth,
        height: allocatedHeight,
        columns: dataset.columns,
        records: this.records,
        sortedRecordIds: this.sortedRecordsIds,
        hasNextPage: paging.hasNextPage,
        hasPreviousPage: paging.hasPreviousPage,
        currentPage: this.currentPage,
        totalResultCount: paging.totalResultCount,
        sorting: dataset.sorting,
        filtering: dataset.filtering && dataset.filtering.getFilter(),
        resources: this.resources,
        itemsLoading: dataset.loading,
        highlightValue: this.context.parameters.HighlightValue.raw,
        highlightColor: this.context.parameters.HighlightColor.raw,
        setSelectedRecords: this.setSelectedRecords,
        onNavigate: this.onNavigate,
        onSort: this.onSort,
        onFilter: this.onFilter,
    }),
    this.container
);

Agregar soporte de pantalla completa

Los componentes de código ofrecen la capacidad de mostrarse en modo de pantalla completa. Esto es especialmente útil en tamaños de pantalla pequeños o donde hay espacio limitado para el componente de código dentro de la pantalla de una aplicación de lienzo.

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';

Para agregar un enlace de pantalla completa, agregue lo siguiente a la Stack existente que contiene los controles de paginación.

Nota

Asegúrese de agregar esto al Stack anidado y no al Stack raíz.

<Stack horizontal style={{ width: '100%', paddingLeft: 8, paddingRight: 8 }}>
    <IconButton
      alt="First Page"
      iconProps={{ iconName: 'Rewind' }}
      disabled={!hasPreviousPage}
      onClick={loadFirstPage}
    />
    <IconButton
      alt="Previous Page"
      iconProps={{ iconName: 'Previous' }}
      disabled={!hasPreviousPage}
      onClick={loadPreviousPage}
    />
    <Stack.Item align="center">
      {stringFormat(
          resources.getString('Label_Grid_Footer'),
          currentPage.toString(),
          selection.getSelectedCount().toString(),
      )}
    </Stack.Item>
    <IconButton
      alt="Next Page"
      iconProps={{ iconName: 'Next' }}
      disabled={!hasNextPage}
      onClick={loadNextPage}
    />
</Stack>

Podrá ver que:

  • Este código usa recursos para mostrar la etiqueta para respaldar la localización.
  • Si el modo de pantalla completa está abierto, no se muestra el enlace. En su lugar, el contexto de la aplicación principal representa automáticamente un icono de cierre.

Agregar props para admitir pantalla completa en GridProps

Agregue los props onFullScreen y isFullScreen a la interfaz GridProps dentro de Grid.tsx para proporcionar funciones de devolución de llamada para ordenar y filtrar:

export interface GridProps {
   width?: number;
   height?: number;
   columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
   records: Record<string, ComponentFramework.PropertyHelper.DataSetApi.EntityRecord>;
   sortedRecordIds: string[];
   hasNextPage: boolean;
   hasPreviousPage: boolean;
   totalResultCount: number;
   currentPage: number;
   sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
   filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
   resources: ComponentFramework.Resources;
   itemsLoading: boolean;
   highlightValue: string | null;
   highlightColor: string | null;
   setSelectedRecords: (ids: string[]) => void;
   onNavigate: (item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord) => void;
   onSort: (name: string, desc: boolean) => void;
   onFilter: (name: string, filtered: boolean) => void;
   loadFirstPage: () => void;
   loadNextPage: () => void;
   loadPreviousPage: () => void;
}

Agregar props para admitir pantalla completa a Grid

Agregue estas nuevas propiedades a la desestructuración de las propiedades:

export const Grid = React.memo((props: GridProps) => {
   const {
      records,
      sortedRecordIds,
      columns,
      width,
      height,
      hasNextPage,
      hasPreviousPage,
      sorting,
      filtering,
      currentPage,
      itemsLoading,
      setSelectedRecords,
      onNavigate,
      onSort,
      onFilter,
      resources,
      loadFirstPage,
      loadNextPage,
      loadPreviousPage,
   } = props;

Actualizar index.ts para admitir la pantalla completa en Grid

Para proporcionar estos nuevos accesorios, en index.ts, agregue el siguiente método de devolución de llamada bajo loadPreviousPage:

onFullScreen = (): void => {
  this.context.mode.setFullScreen(true);
};

La llamada a setFullScreen hace que el componente de código abra el modo de pantalla completa y ajuste allocatedHeight y allocatedWidth en consecuencia, debido a la llamada a trackContainerResize(true) en el método init. Una vez que se abre el modo de pantalla completa, se llamará a updateView, actualizando la representación del componente con el nuevo tamaño. updatedProperties contiene fullscreen_open o fullscreen_close, dependiendo de la transición que se esté produciendo.

Agregue un nuevo campo isFullScreen a la clase CanvasGrid en index.ts para almacenar el estado del modo de pantalla completa:

export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
    notifyOutputChanged: () => void;
    container: HTMLDivElement;
    context: ComponentFramework.Context<IInputs>;
    sortedRecordsIds: string[] = [];
    resources: ComponentFramework.Resources;
    isTestHarness: boolean;
    records: {
        [id: string]: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord;
    };
    currentPage = 1;
    filteredRecordCount?: number;

Edite updateView para rastrear el estado

Agregue lo siguiente al método updateView para rastrear el estado:

public updateView(context: ComponentFramework.Context<IInputs>): void {
    const dataset = context.parameters.records;
    const paging = context.parameters.records.paging;
    const datasetChanged = context.updatedProperties.indexOf("dataset") > -1;
    const resetPaging =
        datasetChanged &&
        !dataset.loading &&
        !dataset.paging.hasPreviousPage &&
        this.currentPage !== 1;

    if (resetPaging) {
        this.currentPage = 1;
    }

Pase la devolución de llamada y el campo isFullScreen para representar en la cuadrícula

Ahora puede pasar la devolución de llamada y el campo isFullScreen en los accesorios de renderizado Grid:

ReactDOM.render(
    React.createElement(Grid, {
        width: allocatedWidth,
        height: allocatedHeight,
        columns: dataset.columns,
        records: this.records,
        sortedRecordIds: this.sortedRecordsIds,
        hasNextPage: paging.hasNextPage,
        hasPreviousPage: paging.hasPreviousPage,
        currentPage: this.currentPage,
        totalResultCount: paging.totalResultCount,
        sorting: dataset.sorting,
        filtering: dataset.filtering && dataset.filtering.getFilter(),
        resources: this.resources,
        itemsLoading: dataset.loading,
        highlightValue: this.context.parameters.HighlightValue.raw,
        highlightColor: this.context.parameters.HighlightColor.raw,
        setSelectedRecords: this.setSelectedRecords,
        onNavigate: this.onNavigate,
        onSort: this.onSort,
        onFilter: this.onFilter,
        loadFirstPage: this.loadFirstPage,
        loadNextPage: this.loadNextPage,
        loadPreviousPage: this.loadPreviousPage,
    }),
    this.container
);

Destacando filas

Ya está listo para agregar la funcionalidad de resaltado de fila condicional. Ya ha definido las propiedades de entrada de HighlightValue y HighlightColor, y el HighlightIndicator property-set. property-set permite al creador elegir un campo para usar para comparar con el valor que proporcionan en HighlightValue.

Importar tipos para admitir el resaltado

La representación de filas personalizadas en DetailsList requiere algunas importaciones adicionales. Ya hay algunos tipos de @fluentui/react/lib/DetailsList, así que agregue IDetailsListProps, IDetailsRowStyles y DetailsRow a esa declaración de importación en Grid.tsx:

import {
    DetailsList,
    ConstrainMode,
    DetailsListLayoutMode,
    IColumn,
    IDetailsHeaderProps
} from '@fluentui/react/lib/DetailsList';

Ahora, cree el representador de filas personalizado agregando lo siguiente justo debajo del bloque const rootContainerStyle:

const onRenderRow: IDetailsListProps['onRenderRow'] = (props) => {
    const customStyles: Partial<IDetailsRowStyles> = {};
    if (props && props.item) {
        const item = props.item as DataSet | undefined;
        if (highlightColor && highlightValue && item?.getValue('HighlightIndicator') == highlightValue) {
            customStyles.root = { backgroundColor: highlightColor };
        }
        return <DetailsRow {...props} styles={customStyles} />;
    }
    return null;
};

Podrá ver que:

  • Puede recuperar el valor del campo elegido por el fabricante a través del alias HighlightIndicator, usando:
    item?.getValue('HighlightIndicator').
  • Cuando el valor del campo HighlightIndicator coincide con el del valor de highlightValue proporcionado por la propiedad de entrada en el componente de código, puede agregar un color de fondo a la fila.
  • El componente DetailsRow es el que utiliza DetailsList para representar las columnas que definió. No es necesario cambiar el comportamiento que no sea el color de fondo.

Agregue accesorios adicionales para apoyar el resaltado

Agregue algunas propiedades adicionales para highlightColor y highlightValue que serán proporcionadas por la representación en el interior de updateView. Ya los tiene agregados a la interfaz GridProps, por lo que solo necesita agregarlos a la desestructuración de propiedades:

export const Grid = React.memo((props: GridProps) => {
   const {
      records,
      sortedRecordIds,
      columns,
      width,
      height,
      hasNextPage,
      hasPreviousPage,
      sorting,
      filtering,
      currentPage,
      itemsLoading,
      setSelectedRecords,
      onNavigate,
      onSort,
      onFilter,
      resources,
      loadFirstPage,
      loadNextPage,
      loadPreviousPage,
      onFullScreen, 
      isFullScreen,
   } = props;

Agregue el método onRenderRow a DetailsList

Pase el método onRenderRow en los accesorios DetailsList:

<DetailsList
  columns={gridColumns}
  onRenderItemColumn={onRenderItemColumn}
  onRenderDetailsHeader={onRenderDetailsHeader}
  items={items}
  setKey={`set${currentPage}`} // Ensures that the selection is reset when paging
  initialFocusedIndex={0}
  checkButtonAriaLabel="select row"
  layoutMode={DetailsListLayoutMode.fixedColumns}
  constrainMode={ConstrainMode.unconstrained}
  selection={selection}
  onItemInvoked={onNavigate}
></DetailsList>

Implementación y configuración del componente

Ahora que ha implementado todas las características, debe implementar el componente de código en Microsoft Dataverse para las pruebas.

  1. Dentro de su entorno de Dataverse, asegúrese de que haya un editor creado con un prefijo de samples:

    Agregar editor nuevo

    Esto también podría ser su propio editor, siempre que actualice el parámetro de prefijo del editor en la llamada a pac pcf push a continuación. Más información: Crear un editor de soluciones.

  2. Una vez que haya guardado el editor, estará listo para autorizar la CLI en su entorno para que podamos enviar el componente de código compilado. En la línea de comandos, use:

    pac auth create --url https://myorg.crm.dynamics.com
    

    Reemplace myorg.crm.dynamics.com con la URL de su propio entorno de Dataverse. Inicie sesión con un usuario Administrador / personalizador cuando se le solicite. Los privilegios proporcionados por estos roles de usuario son necesarios para implementar cualquier componente de código en Dataverse.

  3. Para implementar su componente de código, use:

    pac pcf push --publisher-prefix samples
    

    Nota

    Si recibe el error Missing required tool: MSBuild.exe/dotnet.exe. Please add MSBuild.exe/dotnet.exe in Path environment variable or use 'Developer Command Prompt for VS, debe instalar Visual Studio 2019 para Windows y Mac o Herramientas de compilación para Visual Studio 2019, asegurándose de seleccionar la carga de trabajo 'Herramientas de compilación de .NET' como se describe en los requisitos previos.

  4. Una vez completado, este proceso habrá creado una pequeña solución temporal llamada PowerAppTools_samples en su entorno, y el componente de código CanvasGrid se agregará a esta solución. Puede mover el componente de código a su propia solución más adelante si es necesario. Más información: Componente de código de Administración del ciclo de vida de las aplicaciones (ALM).

    Solución PowerAppsTools_samples

  5. Para usar componentes de código dentro de las aplicaciones de lienzo, debe habilitar Power Apps component framework para aplicaciones de lienzo en el entorno que está utilizando.

    a. Abra el Centro de administración (admin.powerplatform.microsoft.com) y navegue hasta su entorno. b. Vaya a Configuración > Producto > Características. Asegúrese de que Power Apps component framework para aplicaciones de lienzo está Activado:

    Habilitar componentes de código

  6. Cree una nueva aplicación de lienzo utilizando un diseño Tableta.

  7. Desde el panel Insertar, seleccione Obtenga más componentes.

  8. Seleccione la pestaña Código en el panel Importar componentes.

  9. Seleccione el componente CanvasGrid.

  10. Seleccione Importar. El componente de código ahora aparecerá en Componentes de código en el panel Insertar.

  11. Arrastre el compoente CanvasGrid en la pantalla y enlace con la tabla Contacts en Microsoft Dataverse.

  12. Establezca las siguientes propiedades en el componente de código CanvasGrid usando el panel de propiedades:

    • Resaltar valor = 1: este es el valor que tiene statecode cuando el registro está inactivo.
    • Resaltar color = #FDE7E9: este es el color que se utilizará cuando el registro esté inactivo.
    • HighlightIndicator = "statecode"- Este es el campo con el que comparar. Esto estará en el panel Avanzado en la sección DATOS.

    Panel Propiedades

  13. Agregue un nuevo componente TextInput y llámelo txtSearch.

  14. Actualice la propiedad CanvasGrid.Items para ser Search(Contacts,txtSearch.Text,"fullname").

    A medida que escriba en la Entrada de texto, verá que los contactos se filtran en la cuadrícula.

  15. Agregue una nueva Etiqueta de texto y establezca el texto en "No se encontraron registros". Coloque la etiqueta en la parte superior de la cuadrícula de lienzo.

  16. Establezca la propiedad Visible de la etiqueta Texto en CanvasGrid1.FilteredRecordCount=0.

Esto significa que cuando no haya registros que coincidan con el valor txtSearch, o si se aplica un filtro de columna utilizando el menú contextual que no devuelve registros (por ejemplo, el nombre completo no contiene datos), se mostrará la etiqueta.

  1. Agregue un Formulario de visualización (desde el grupo Entrada en el panel Insertar).

  2. Establezca el formulario DataSource hacia la tabla Contacts y agregue algunos campos de formulario.

  3. Establezca la propiedad del formulario Item en CanvasGrid1.Selected.

    Ahora debería ver que cuando selecciona elementos en la cuadrícula, el formulario muestra el elemento seleccionado.

  4. Agregue una Pantalla nueva a la aplicación de lienzo llamada scrDetails.

  5. Copie el formulario de la pantalla anterior y péguelo en la nueva pantalla.

  6. Establezca la propiedad CanvasGrid1.OnSelect en Navigate(scrDetails).

    Cuando invoca la acción de selección de fila de la cuadrícula, ahora debería ver que la aplicación navega a la segunda pantalla con el elemento seleccionado.

Depurar después de implementar

Puede depurar fácilmente su componente de código mientras se ejecuta en la aplicación de lienzo abriendo las Herramientas de desarrollo con Ctrl+Shift+I.

Seleccione Ctrl+P y escriba Grid.tsx o Index.ts. A continuación, puede establecer un punto de interrupción y recorrer su código.

Depuración aplicaciones de lienzo

Si necesita realizar más cambios en su componente, no necesita implementarlo cada vez. En su lugar, utilice la técnica descrita en Componentes del código de depuración para crear un Fiddler AutoResponder para cargar el archivo desde su sistema de archivos local mientras se ejecuta npm start watch.

La Respuesta automática se vería similar a lo siguiente:

REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.CanvasGrid[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\CanvasGrid\out\controls\CanvasGrid\${folder}\${fname}

Regla de respuesta automática

También necesitará habilitar los filtros para agregar el encabezado Access-Control-Allow-Origin. Más información: Depuración después de implementar en Microsoft Dataverse.

Necesitara Caché vacío y actualización completa en la sesión de su navegador para que el archivo AutoResponder sea recogido. Una vez cargado, simplemente puede actualizar el navegador, ya que Fiddler agregará un encabezado de control de caché al archivo para evitar que se almacene en caché.

Una vez que esté satisfecho con sus cambios, puede incrementar la versión del parche en el manifiesto y luego volver a implementar usando pac pcf push..

Hasta ahora, ha implementado una compilación de desarrollo, que no está optimizada y se ejecutará más lentamente en el tiempo de ejecución. Puede optar por implementar una compilación optimizada utilizando pac pcf push editando el CanvasGrid.pcfproj. Debajo de OutputPath, agregue lo siguiente: <PcfBuildMode>production</PcfBuildMode>

  <PropertyGroup>
    <Name>CanvasGrid</Name>
    <ProjectGuid>a670bba8-e0ae-49ed-8cd2-73917bace346</ProjectGuid>
    <OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
  </PropertyGroup>

Administración del ciclo de vida de la aplicación (ALM) con Microsoft Power Platform
Referencia de la API de Power Apps component framework
Crear el primer componente
Depurar componentes de código

Nota

¿Puede indicarnos sus preferencias de idioma de documentación? Realice una breve encuesta. (tenga en cuenta que esta encuesta está en inglés)

La encuesta durará unos siete minutos. No se recopilan datos personales (declaración de privacidad).