Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Neste tutorial, você criará um componente de código para dataset em um aplicativo Canvas, o implantará, adicioná-lo à interface de usuário, e testará o componente usando Visual Studio Code. O componente de código exibe uma grade de conjunto de dados paginável e rolável que fornece colunas classificáveis e filtradas. Ele também permite o realce de linhas específicas configurando uma coluna de indicador. Essa é uma solicitação comum dos criadores de aplicativos e pode ser complexa de implementar usando componentes de aplicativo de tela nativa. Os componentes de código podem ser escritos para funcionar em aplicativos de canvas e em aplicativos de modelo. No entanto, esse componente foi desenvolvido especificamente para ser usado em aplicativos de canvas.
Além desses requisitos, você também garantirá que o componente de código siga as diretrizes de prática recomendada:
- Uso da interface do usuário do Microsoft Fluent
- Localização dos rótulos dos componentes de código tanto no design quanto no tempo de execução
- Garantir que o componente de código seja renderizado na largura e altura fornecidas pela tela do aplicativo de canvas pai
- Consideração para o criador de aplicativos personalizar a interface do usuário usando propriedades de entrada e elementos de aplicativo externos na medida do possível
Observação
Antes de começar, verifique se você instalou todos os componentes de pré-requisito.
Code
Você pode baixar o exemplo completo do PowerApps-Samples/component-framework/CanvasGridControl/.
Criar um novo projeto pcfproj
Crie uma nova pasta a ser usada para o componente de código. Por exemplo,
C:\repos\CanvasGrid.Abra Visual Studio Code e File>Open Folder e selecione a pasta
CanvasGrid. Se você adicionou as extensões Windows Explorer durante a instalação do Visual Studio Code, poderá usar a opção de menu de contexto Open com Code dentro da pasta. Você também pode carregar qualquer pasta em Visual Studio Code usandocode .no prompt de comando quando o diretório atual estiver definido para esse local.Dentro de um novo terminal do PowerShell Visual Studio Code (Terminal>New Terminal), use o comando pac pcf init para criar um novo projeto de componente de código:
pac pcf init --namespace SampleNamespace --name CanvasGrid --template datasetou usando o formulário curto:
pac pcf init -ns SampleNamespace -n CanvasGrid -t datasetIsso adiciona arquivos novos
pcfproje relacionados à pasta atual, incluindo umpackages.jsonque define os módulos necessários. Para instalar os módulos necessários, use a instalação do npm:npm installObservação
Se você receber a mensagem,
The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program.verifique se instalou todos os pré-requisitos, especificamente node.js (a versão lts é recomendada).
O modelo inclui um index.ts arquivo junto com vários arquivos de configuração. Esse é o ponto inicial do componente de código e contém os métodos de ciclo de vida descritos na implementação do componente.
Instalar a interface do usuário do Microsoft Fluent
Você usará o Microsoft Fluent UI e o React para criar a interface do usuário, portanto, deve instalá-los como dependências. Use o seguinte no terminal:
npm install react react-dom @fluentui/react
Isso acrescenta os módulos ao packages.json e os instala no diretório node_modules. Você não fará o commit de node_modules no controle de código-fonte, pois todos os módulos necessários podem ser restaurados usando npm install.
Uma das vantagens da interface do usuário do Microsoft Fluent é que ela fornece uma interface do usuário consistente e altamente acessível .
Configurando eslint
O modelo usado pelo pac pcf init instala o eslint módulo em seu projeto e o configura adicionando um .eslintrc.json arquivo.
Eslint agora requer a configuração para os estilos de codificação TypeScript e React. Mais informações: Linting – Práticas recomendadas e diretrizes para componentes de código.
Definir as propriedades do conjunto de dados
O CanvasGrid\ControlManifest.Input.xml arquivo define os metadados que descrevem o comportamento do componente de código. O atributo de controle já conterá o namespace e o nome do componente.
Dica
Você pode achar o XML mais fácil de ler formatando-o para que os atributos apareçam em linhas separadas. Localize e instale uma ferramenta de formatação XML de sua escolha no Visual Studio Code Marketplace: Search para extensões de formatação xml.
Os exemplos abaixo foram formatados com atributos em linhas separadas para facilitar a leitura.
Você deve definir os registros aos quais o componente de código pode ser associado, adicionando o seguinte dentro do control elemento, substituindo o elemento existente data-set :
O conjunto de dados registros será associado a uma fonte de dados quando o componente de código for adicionado a um aplicativo de tela. O conjunto de propriedades indica que o usuário deve configurar uma das colunas desse conjunto de dados a serem usadas como o indicador de realce de linha.
Dica
Você pode especificar vários elementos de conjunto de dados. Isso pode ser útil se você quiser pesquisar um dataset, mas mostrar uma lista de registros usando um segundo dataset.
Definindo as propriedades de entrada e saída
Além do conjunto de dados, você pode fornecer as seguintes propriedades de entrada :
-
HighlightValue- Permite que o criador de aplicativos forneça um valor a ser comparado com a coluna definida comoHighlightIndicatorproperty-set. Quando os valores são iguais, a linha deve ser realçada. -
HighlightColor- Permite que o criador de aplicativos selecione uma cor a ser usada para realçar linhas.
Dica
Ao criar componentes de código para uso em aplicativos canvas, é recomendável fornecer propriedades de entrada para a estilização de elementos comuns dos componentes de código.
Além das propriedades de entrada, uma propriedade de saída nomeada FilteredRecordCount será atualizada (e disparará o OnChange evento) quando a contagem de linhas for alterada devido a uma ação de filtro aplicada dentro do componente de código. Isso é útil quando você deseja exibir uma mensagem No Rows Found dentro do aplicativo pai.
Observação
No futuro, os componentes de código darão suporte a eventos personalizados para que você possa definir um evento específico em vez de usar o evento genérico OnChange .
Para definir essas três propriedades, adicione o seguinte ao CanvasGrid\ControlManifest.Input.xml arquivo, abaixo do 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"/>
Salve este arquivo e, em seguida, na linha de comando, use:
npm run build
Observação
Se você receber um erro como este durante a execução 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 arquivo e adicione o seguinte: // eslint-disable-next-line no-undef, diretamente acima da linha:
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
Execute a npm run build novamente.
Depois que o componente for criado, você verá que:
Um arquivo
CanvasGrid\generated\ManifestTypes.d.tsgerado automaticamente é adicionado ao seu projeto. Isso é gerado como parte do processo de build a partir doControlManifest.Input.xmle fornece os tipos para interagir com as propriedades de entrada/saída.A saída de build é adicionada à
outpasta. Ébundle.jso JavaScript transpilado que é executado dentro do navegador, eControlManifest.xmlé uma versão refatorada do arquivoControlManifest.Input.xmlque é usada durante a implementação.Observação
Não modifique diretamente o conteúdo das pastas
generatedeout. Eles serão substituídos como parte do processo de build.
Adicionar o componente Grid Fluent UI React
Quando o componente de código usa React, deve haver um único componente raiz renderizado dentro do método updateView. Dentro da CanvasGrid pasta, adicione um novo arquivo TypeScript chamado Grid.tsxe adicione o seguinte conteúdo:
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';
Observação
O arquivo tem a extensão tsx que é um arquivo TypeScript que dá suporte à sintaxe de estilo XML usada pelo React. Ele é compilado em JavaScript padrão pelo processo de build.
Notas de design de grade
Esta seção inclui comentários sobre o design do Grid.tsx componente.
É um componente funcional
Esse é um componente funcional do React, mas, igualmente, pode ser um componente de classe. Isso se baseia no seu estilo de codificação preferido. Componentes de classe e componentes funcionais também podem ser misturados no mesmo projeto. Os componentes de função e classe usam a tsx sintaxe de estilo XML usada pelo React. Mais informações: Componentes de função e classe
Minimizar o tamanho do bundle.js
Ao importar os componentes Fluent UI ChoiceGroup usando importações baseadas em caminho, em vez 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';
Dessa forma, o tamanho do pacote será menor, resultando em requisitos de capacidade mais baixos e melhor desempenho de runtime.
Uma alternativa seria usar o tree-shaking.
Atribuição de desestruturação
Esse código:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
Usa a atribuição de estruturação. Dessa forma, você extrai os atributos necessários para renderizar dos adereços, em vez de prefixá-los com props. cada vez que eles são usados.
O código também usa React.memo para encapsular o componente funcional para que ele não seja renderizado, a menos que qualquer um dos adereços de entrada tenha sido alterado.
Uso de React.useMemo
React.useMemo é utilizado em vários lugares para garantir que a matriz de itens criada seja alterada apenas quando as propriedades de entrada, options ou configuration, forem modificadas. Essa é uma prática recomendada de componentes de função que reduz renderizações desnecessárias dos componentes filho.
Outros itens a serem observados:
- O
DetailsListem umStacké envolvido porque, posteriormente, você adicionará um elemento de rodapé com os controles de paginação. - O componente Fluent UI
Stickyé usado para encapsular as colunas de cabeçalho (usandoonRenderDetailsHeader) para que elas permaneçam visíveis quando a grade é rolada. -
setKeyé passado para oDetailsListjunto cominitialFocusedIndexde modo que, quando a página atual for alterada, a posição de rolagem e a seleção serão redefinidas. - A função
onRenderItemColumné usada para renderizar o conteúdo da célula. Ele aceita o item de linha e usa getFormattedValue para retornar o valor de exibição da coluna. O método getValue retorna um valor que você pode usar para fornecer uma renderização alternativa. A vantagem degetFormattedValueé que ele contém uma cadeia de caracteres formatada para colunas de tipos não textuais, como datas e consultas. - O bloco
gridColumnsestá mapeando a forma do objeto das colunas fornecidas pelo contexto do conjunto de dados para a forma esperada pela propriedade colunas deDetailsList. Como isso está encapsulado no React.useMemo hook, a saída só será alterada quando as propriedadescolumnsousortingforem alteradas. Você pode exibir os ícones de classificação e filtro nas colunas em que os detalhes de classificação e filtragem fornecidos pelo contexto do componente de código correspondem à coluna que está sendo mapeada. As colunas são classificadas usando acolumn.orderpropriedade para garantir que elas estejam na ordem correta na grade, conforme definido pelo criador de aplicativos. - Você está mantendo um estado interno para
isComponentLoadingno nosso componente React. Isso ocorre porque, quando o usuário seleciona ações de classificação e filtragem, você pode esmaecer a grade como uma pista visual até que o elementosortedRecordIdsseja atualizado e o estado seja redefinido. Há uma propriedade de entrada adicional chamadaitemsLoadingque é mapeada para a propriedade dataset.loading fornecida pelo contexto de dados. Ambos os flags são usados para controlar a indicação de carregamento visual implementada usando o componente Fluent UIOverlay.
Atualizar index.ts
A próxima etapa é fazer alterações no index.ts arquivo para corresponder às propriedades definidas em Grid.tsx.
Adicionar instruções de importação e inicializar ícones
Para o cabeçalho de index.ts, substitua as importações existentes pelas seguintes:
import {IInputs, IOutputs} from './generated/ManifestTypes';
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
Observação
A importação de initializeIcons é necessária porque esse código usa o conjunto de ícones do Fluent UI. Você chama initializeIcons para carregar os ícones dentro do cinto de teste. Dentro de aplicativos de 'canvas', já estão inicializados.
Adicionar campos à classe CanvasGrid
Adicione os seguintes campos à CanvasGrid classe:
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor() {
}
Atualizar o método de inicialização
Adicione o seguinte a init:
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary,
container: HTMLDivElement): void {
// Add control initialization code
}
A init função é chamada quando o componente de código é inicializado pela primeira vez em uma tela do aplicativo. Você armazena uma referência para o seguinte:
-
notifyOutputChanged: Esta é a função de retorno de chamada que você utiliza para notificar o aplicativo Canvas de que uma das propriedades foi alterada. -
container:Este é o elemento DOM ao qual você adiciona a interface do usuário do componente de código. -
resources:Isso é usado para recuperar cadeias de caracteres localizadas no idioma do usuário atual.
O context.mode.trackContainerResize(true)) é utilizado para que updateView seja chamado quando o elemento de código alterar seu tamanho.
Observação
Atualmente, não há como determinar se o componente de código está em execução dentro do cinto de teste. Você precisa detectar se o control-dimensionsdiv elemento está presente como um indicador.
Atualizar o método updateView
Adicione o seguinte a updateView:
public updateView(context: ComponentFramework.Context<IInputs>): void {
// Add code to update control view
}
Você pode ver que:
- Você chama React.createElement, passando a referência para o container DOM recebido dentro da função
init. - O
Gridcomponente é definido dentroGrid.tsxe é importado na parte superior do arquivo. - O
allocatedWidtheallocatedHeightserão fornecidos pelo contexto pai sempre que houver uma alteração (por exemplo, o aplicativo redimensiona o componente de código ou você entra no modo de tela inteira), já que você fez uma chamada para trackContainerResize(true) dentro da funçãoinit. - Você pode detectar quando há novas linhas a serem exibidas quando a matriz updatedProperties contiver a
datasetcadeia de caracteres. - No ambiente de teste, a
updatedPropertiesmatriz não é preenchida, portanto, você pode usar oisTestHarnesssinalizador que você definiu nainitfunção para interromper a lógica que define osortedRecordIderecords. Você mantém uma referência aos valores atuais até que eles sejam alterados, de modo que você não os altere quando passado para o componente filho, a menos que seja necessária uma nova renderização dos dados. - Como o componente de código mantém o estado da página exibida, o número da página é redefinido quando o contexto pai redefine os registros para a primeira página. Você sabe quando está de volta à primeira página quando
hasPreviousPageé falso.
Atualizar o método destroy
Por fim, você precisa arrumar quando o componente de código é destruído:
Iniciar o arreio de teste
Verifique se todos os arquivos foram salvos e, no terminal, use:
npm start watch
Você precisa definir a largura e a altura para ver a grade de componentes de código preenchida usando os três registros de exemplo. Em seguida, você pode exportar um conjunto de registros para um arquivo CSV do Dataverse e carregá-lo no harness de teste usando o painel Entradas>de Registros de Dados.
Aqui estão alguns dados de exemplo separados por vírgula que você pode salvar em um arquivo .csv e 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)
Observação
Apenas uma coluna é mostrada no harnês de teste, independentemente das colunas fornecidas no arquivo CSV carregado. Isso ocorre porque a ferramenta de teste apenas mostra property-set quando há uma definida. Se não for property-set definido, todas as colunas no arquivo CSV carregado serão preenchidas.
Adicionar seleção de linha
Embora a interface DetailsList do usuário fluente permita a seleção de registros por padrão, os registros selecionados não estão vinculados à saída do componente de código. Você precisa das propriedades Selected e SelectedItems para refletir os registros escolhidos em um app de tela, para que os componentes relacionados possam ser atualizados. Neste exemplo, você permite a seleção de apenas um único item de cada vez, portanto SelectedItems , só conterá um único registro.
Atualizar importações grid.tsx
Adicione o seguinte às importações dentro 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';
Adicionar setSelectedRecords a GridProps
Para a GridProps interface, dentro de Grid.tsx, adicione o seguinte:
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;
}
Adicionar a propriedade setSelectedRecords ao Grid
Dentro do componente de função Grid.tsx, atualize a desestruturação do props para adicionar a nova propriedade setSelectedRecords.
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
Diretamente abaixo disso, adicione:
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,
});
});
Os ganchos React.useCallback e useConst garantem que esses valores não sejam modificados entre renderizações e causem renderização de componente filho desnecessária.
O gancho useForceUpdate garante que, quando a seleção for atualizada, o componente seja renderizado novamente para refletir a contagem de seleção atualizada.
Adicionar seleção a DetailsList
O selection objeto criado para manter o estado da seleção é então passado para o DetailsList componente:
<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 o callback de setSelectedRecords
Você precisa definir o novo setSelectedRecords callback dentro de index.ts e passá-lo para o componente Grid. Próximo à parte superior da CanvasGrid classe, adicione o seguinte:
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;
Observação
O método é definido como uma função de seta para associá-lo à instância atual this do componente de código.
A chamada para setSelectedRecordIds informa ao aplicativo de tela que a seleção foi alterada para que os outros componentes que referenciam SelectedItems e Selected sejam atualizados.
Adicionar novo callback às propriedades de entrada
Por fim, adicione o novo callback às propriedades de entrada do componente Grid no 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 o OnSelect evento
Há um padrão em aplicativos Canvas em que, se uma galeria ou grade tiver uma seleção de item invocada (por exemplo, selecionando um ícone de seta), ele aciona o evento OnSelect. Você pode implementar esse padrão usando o método openDatasetItem do conjunto de dados.
Adicionar onNavigate à interface GridProps
Como antes, você adiciona uma propriedade de retorno de chamada adicional ao componente Grid adicionando o seguinte na interface GridProps dentro de Grid.tsx:
export interface GridProps {
width?: number;
height?: number;
columns: ComponentFramework.PropertyHelper.DataSetApi.Column[];
records: Record<
string,
ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
>;
sortedRecordIds: string[];
hasNextPage: boolean;
hasPreviousPage: boolean;
totalResultCount: number;
currentPage: number;
sorting: ComponentFramework.PropertyHelper.DataSetApi.SortStatus[];
filtering: ComponentFramework.PropertyHelper.DataSetApi.FilterExpression;
resources: ComponentFramework.Resources;
itemsLoading: boolean;
highlightValue: string | null;
highlightColor: string | null;
setSelectedRecords: (ids: string[]) => void;
}
Adicionar onNavigate a adereços de grade
Novamente, você deve adicionar o novo adereço à destruidora dos adereços:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
} = props;
Adicionar onItemInvoked ao DetailsList
O DetailList tem um adereço de retorno de chamada chamado onItemInvoked que, por sua vez, você passa o retorno de chamada para:
<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>
Adicionar o método onNavigate ao index.ts
Adicione o onNavigate método ao index.ts logo abaixo do setSelectedRecords método:
onNavigate = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
): void => {
if (item) {
this.context.parameters.records.openDatasetItem(item.getNamedReference());
}
};
Isso simplesmente invoca o openDatasetItem método no registro do conjunto de dados para que o componente de código acione o OnSelect evento. O método é definido como uma função de seta para associá-lo à instância atual this do componente de código.
Você precisa passar essa ação de retorno para as propriedades do componente Grid dentro do 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
);
Quando você salvar todos os arquivos, o ambiente de teste recarregará. Use Ctrl + Shift + I(ou F12) e use Abrir Arquivo (Ctrl + P) pesquisando index.ts e você pode colocar um ponto de interrupção dentro do método onNavigate. Clicar duas vezes em uma linha (ou realçá-la com as teclas de cursor e pressionar Enter) atingirá o ponto de interrupção porque DetailsList invoca o retorno de chamada onNavigate.
Há uma referência a _this porque a função é definida como uma função arrow e foi transpilada em um closure JavaScript para capturar a instância de this.
Adicionar Localização
Antes de ir mais longe, você precisa adicionar cadeias de caracteres de recurso ao componente de código para que você possa usar cadeias de caracteres localizadas para mensagens como paginação, classificação e filtragem. Adicione um novo arquivo CanvasGrid\strings\CanvasGrid.1033.resx e use o editor de recursos Visual Studio ou Visual Studio Code com uma extensão para inserir o seguinte:
| Nome | Valor |
|---|---|
Records_Dataset_Display |
Arquivos |
FilteredRecordCount_Disp |
Contagem de registros filtrados |
FilteredRecordCount_Desc |
O número de registros após a filtragem |
HighlightValue_Disp |
Realçar valor |
HighlightValue_Desc |
O valor para indicar uma linha deve ser realçado |
HighlightColor_Disp |
Cor do Realce |
HighlightColor_Desc |
A cor para realçar uma linha usando |
HighlightIndicator_Disp |
Campo Indicador de Destaque |
HighlightIndicator_Desc |
Configurar o nome do campo para ser comparado com o Valor de Destaque |
Label_Grid_Footer |
Página {0} ({1} Selecionada) |
Label_SortAZ |
De A a Z |
Label_SortZA |
Z para A |
Label_DoesNotContainData |
Não contém dados |
Label_ShowFullScreen |
Mostrar Tela Inteira |
Dica
Não é recomendável editar resx arquivos diretamente. Em vez disso, use o editor de recursos do Visual Studio ou uma extensão para Visual Studio Code. Encontre uma extensão Visual Studio Code: Pesquise no Visual Studio Marketplace por um editor resx
Os dados desse arquivo também podem ser definidos abrindo o CanvasGrid.1033.resx arquivo no Bloco de Notas e copiando o conteúdo XML abaixo:
<?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>
Você tem cadeias de caracteres de recurso para as propriedades input, /, output e as dataset associadas a property-set. Elas serão usadas no Power Apps Studio no momento de design com base na linguagem do navegador do criador. Você também pode adicionar cadeias de caracteres de rótulo que podem ser recuperadas em runtime usando getString. Mais informações: Implementando o componente de API de localização.
Adicione este novo arquivo de recurso ao ControlManifest.Input.xml arquivo dentro do resources elemento:
Adicionar classificação e filtragem de colunas
Se você quiser permitir que o usuário classifique e filtre usando cabeçalhos de coluna de grade, o Fluent UI DetailList fornece uma maneira fácil de adicionar menus de contexto aos cabeçalhos de coluna.
Adicionar onSort e onFilter a GridProps
Primeiro, adicione onSort e onFilter à interface GridProps dentro de Grid.tsx para fornecer funções de retorno de chamada para classificação e filtragem.
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;
}
Adicionar onSort, onFilter e recursos a adereços
Em seguida, adicione esses novos adereços junto com a resources referência (para que você possa recuperar rótulos localizados para classificação e filtragem) aos adereços que se destruem:
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
} = props;
Importar componentes ContextualMenu
Você precisa adicionar algumas importações ao início de Grid.tsx para que você possa usar o componente ContextualMenu fornecido pelo Fluent UI. Você pode usar importações baseadas em caminho para reduzir o tamanho do pacote.
import { ContextualMenu, DirectionalHint, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu';
Adicionar funcionalidade de renderização do menu de contexto
Agora, adicione a funcionalidade de renderização do menu de contexto no Grid.tsx, logo abaixo da linha
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);:
Você verá isso:
- O
contextualMenuPropsestado controla a visibilidade do menu de contexto renderizado usando o componente Fluent UIContextualMenu. - Esse código fornece um filtro simples para mostrar apenas valores em que o campo não contém dados. Você pode estender isso para fornecer filtragem adicional.
- Esse código usa
resources.getStringpara mostrar rótulos no menu de contexto que podem ser localizados. - O
React.useCallbackgancho, semelhante aReact.useMemo, garante que os retornos de chamada sejam modificados apenas quando os valores dependentes forem alterados. Isso otimiza a renderização de componentes filho.
Adicionar novas funções de menu de contexto aos eventos de seleção de coluna e de menu de contexto
Adicione essas novas funções de menu de contexto aos eventos de seleção de coluna e de menu de contexto. Atualize o const gridColumns para adicionar os retornos de chamada onColumnContextMenu e 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]);
Adicionar menu de contexto à saída renderizada
Para que o menu de contexto seja mostrado, você precisa adicioná-lo à saída renderizada. Adicione o seguinte diretamente abaixo do componente DetailsList na saída retornada:
<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>
Adicionar funções onSort e OnFilter
Agora que você adicionou a interface do usuário de classificação e filtragem, você precisa adicionar os callbacks para index.ts para realmente realizar a classificação e filtragem nos registros vinculados ao componente de código. Adicione o seguinte logo abaixo de index.ts, à função 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();
};
Você verá isso:
- A classificação e o filtro são aplicados ao conjunto de dados usando as propriedades de classificação e filtragem .
- Ao modificar as colunas de classificação, as definições de classificação existentes devem ser removidas usando pop em vez de substituir a própria matriz de classificação.
- A atualização deve ser chamada após a classificação e a filtragem ser aplicada. Se um filtro e uma classificação forem aplicados ao mesmo tempo, a atualização só precisará ser chamada uma vez.
Adicionar callbacks OnSort e OnFilter à renderização do Grid
Por fim, você pode passar essas duas funções de retorno para a Grid chamada de renderização.
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
);
Observação
Neste ponto, você não pode mais testar o uso do cinto de teste porque ele não fornece suporte para classificação e filtragem. Posteriormente, você pode implantar usando o pac pcf push e depois adicionar a um aplicativo canvas para teste. Se desejar, você pode pular para essa etapa para ver como o componente de código fica dentro de aplicativos canvas.
Atualizar a propriedade de saída FilteredRecordCount
Como a grid agora pode filtrar registros internamente, é importante informar ao aplicativo canvas quantos registros são exibidos. Isso é para que você possa mostrar uma mensagem de tipo "Sem Registros".
Dica
Você pode implementar isso internamente no componente de código; no entanto, é recomendável que o máximo da interface do usuário seja implementado no aplicativo canvas, pois isso dará ao criador mais flexibilidade.
Você já definiu uma propriedade de saída chamada FilteredRecordCount no ControlManifest.Input.xml. Quando a filtragem ocorrer e os registros filtrados forem carregados, a updateView função será chamada com cadeia de caracteres dataset na matriz updatedProperties . Se o número de registros tiver sido alterado, você precisará fazer uma chamada para notifyOutputChanged para que o aplicativo canvas saiba que deve atualizar todos os controles que usam a propriedade FilteredRecordCount. Dentro do updateView método de index.ts, adicione o seguinte logo acima do ReactDOM.render e abaixo allocatedHeight:
Adicionar FilteredRecordCount a getOutputs
Isso atualiza a filteredRecordCount classe de componente de código que você definiu anteriormente quando ela é diferente dos novos dados recebidos. Depois que notifyOutputChanged for chamado, você precisa garantir que o valor seja retornado quando getOutputs for chamado, então atualize o método getOutputs para ser:
Adicionar paginação à grade
Para grandes conjuntos de dados, os aplicativos de tela dividirão os registros em várias páginas. Você pode adicionar um rodapé que mostra os controles de navegação de página. Cada botão será renderizado usando o Fluent UI IconButton, que você deve importar.
Adicionar IconButton às importações
Adicione isso às importações dentro Grid.tsx:
import { IconButton } from '@fluentui/react/lib/Button';
Adicionar função stringFormat
A etapa a seguir adicionará capacidades para carregar o formato do rótulo do indicador de página a partir das cadeias de caracteres de recursos ("Page {0} ({1} Selected)") e formatar usando uma função simples stringFormat. Essa função também pode estar em um arquivo separado e compartilhada entre seus componentes por conveniência:
Neste tutorial, adicione-o na parte superior de Grid.tsx, logo abaixo type DataSet ....
function stringFormat(template: string, ...args: string[]): string {
for (const k in args) {
template = template.replace("{" + k + "}", args[k]);
}
return template;
}
Adicionar botões de paginação
In Grid.tsx, adicione o seguinte Stack.Item abaixo do existente Stack.Item que contém o 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>
);
Você verá isso:
- O
Stackgarante que o rodapé ficará posicionado abaixo doDetailsList. Ogrowatributo é usado para garantir que a grade se expanda para preencher o espaço disponível. - Você carrega o formato do rótulo de indicador de página das cadeias de caracteres de recurso (
"Page {0} ({1} Selected)") e formata usando a funçãostringFormatque você adicionou na etapa anterior. - Você pode fornecer
alttexto para acessibilidade na paginaçãoIconButtons. - O estilo no rodapé também pode ser aplicado usando um nome de classe CSS referenciando um arquivo CSS adicionado ao componente de código.
Adicionar adereços de retorno de chamada para dar suporte à paginação
Em seguida, você deve adicionar as propriedades ausentes loadFirstPage, loadNextPage e loadPreviousPage de retorno de chamada.
Para a GridProps interface, adicione o seguinte:
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;
}
Adicionar novas propriedades de paginação ao Grid
Adicione estes novos adereços à destruidora de adereços:
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;
Adicionar retornos de chamada ao index.ts
Adicione esses retornos de chamada ao index.ts método abaixo 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);
};
Em seguida, atualize a Grid chamada de renderização para incluir estes retornos de chamada:
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
);
Adicionar suporte de tela inteira
Os componentes de código oferecem a capacidade de mostrar no modo de tela inteira. Isso é especialmente útil em tamanhos de tela pequenos ou onde há espaço limitado para o componente de código em uma tela de aplicativo canvas.
Importar o componente Link do Fluent UI
Para iniciar modo de tela inteira, você pode usar o componente Fluent UI Link. Adicione-o às importações na parte superior de Grid.tsx:
import { Link } from '@fluentui/react/lib/Link';
Adicionar o controle Link
Para adicionar um link de tela inteira, adicione o seguinte ao existente Stack que contém os controles de paginação.
Observação
Certifique-se de adicionar isso ao elemento aninhado Stack, e não ao elemento raiz Stack.
<Stack horizontal style={{ width: '100%', paddingLeft: 8, paddingRight: 8 }}>
<IconButton
alt="First Page"
iconProps={{ iconName: 'Rewind' }}
disabled={!hasPreviousPage}
onClick={loadFirstPage}
/>
<IconButton
alt="Previous Page"
iconProps={{ iconName: 'Previous' }}
disabled={!hasPreviousPage}
onClick={loadPreviousPage}
/>
<Stack.Item align="center">
{stringFormat(
resources.getString('Label_Grid_Footer'),
currentPage.toString(),
selection.getSelectedCount().toString(),
)}
</Stack.Item>
<IconButton
alt="Next Page"
iconProps={{ iconName: 'Next' }}
disabled={!hasNextPage}
onClick={loadNextPage}
/>
</Stack>
Você verá isso:
- Esse código usa recursos para mostrar o rótulo para dar suporte à localização.
- Se o modo de tela inteira estiver aberto, o link não será mostrado. Em vez disso, o contexto do aplicativo pai renderiza automaticamente um ícone de fechamento.
Adicionar adereços para dar suporte a tela inteira a GridProps
Adicione os props onFullScreen e isFullScreen à interface GridProps dentro de Grid.tsx para fornecer callbacks para classificação e filtragem.
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;
}
Adicionar adereços para dar suporte à tela inteira à Grade
Adicione estes novos adereços à destruidora de adereços:
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;
Atualizar index.ts para dar suporte à tela inteira para a Grade
Para fornecer esses novos props, dentro de index.ts, adicione o seguinte método callback abaixo de loadPreviousPage:
onFullScreen = (): void => {
this.context.mode.setFullScreen(true);
};
A chamada para setFullScreen faz com que o componente de código abra o modo de tela inteira e ajuste allocatedHeight e allocatedWidth adequadamente por causa da chamada para trackContainerResize(true) no método init. Depois que o modo de tela inteira estiver aberto, updateView será chamado, atualizando a renderização do componente com o novo tamanho. O updatedProperties contém fullscreen_open ou fullscreen_close, dependendo da transição que está acontecendo.
Para armazenar o estado do modo de tela inteira, adicione um novo isFullScreen campo à CanvasGrid classe dentro 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 acompanhar o estado
Adicione o seguinte ao updateView método para acompanhar o 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;
}
Passe o callback e o campo isFullScreen para renderizar no Grid
Agora você pode passar o callback e o campo isFullScreen para as propriedades de renderização 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
);
Realçando linhas
Agora você está pronto para adicionar a funcionalidade de destaque de linha condicional. Você já definiu as HighlightValue e HighlightColor propriedades de entrada, e as HighlightIndicatorproperty-set. "O property-set permite que o criador escolha um campo para comparar com o valor fornecido em HighlightValue."
Importar tipos para dar suporte ao realce
A renderização de linha personalizada no DetailsList requer algumas importações adicionais. Já existem alguns tipos de @fluentui/react/lib/DetailsList, então adicione IDetailsListProps, IDetailsRowStyles e DetailsRow a essa instrução de importação em Grid.tsx:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps
} from '@fluentui/react/lib/DetailsList';
Agora, crie o renderizador de linha personalizado adicionando o seguinte logo abaixo do const rootContainerStyle bloco:
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;
};
Você verá isso:
- Você pode recuperar o valor do campo escolhido pelo criador por meio do alias
HighlightIndicatorusando:
item?.getValue('HighlightIndicator'). - Quando o valor do campo
HighlightIndicatorcorresponde ao valor fornecido pela propriedade de entradahighlightValueno componente de código, você pode adicionar uma cor de plano de fundo à linha. - O
DetailsRowcomponente é usado pelaDetailsListpara renderizar as colunas que você definiu. Você não precisa alterar o comportamento que não seja a cor da tela de fundo.
Adicionar adereços adicionais para dar suporte ao realce
Adicione algumas propriedades adicionais para highlightColor e highlightValue que serão fornecidas pela renderização dentro de updateView. Você já adicionou à GridProps interface, então você só precisa adicioná-los à desestruturação de props.
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;
Adicionar o método onRenderRow à DetailsList
Passe o método onRenderRow nas propriedades 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>
Implantar e configurar o componente
Agora que você implementou todos os recursos, deve implantar o componente de código no Microsoft Dataverse para testes.
Dentro do ambiente do Dataverse, verifique se há um publicador criado com um prefixo de
samples:
Esse também pode ser seu próprio publicador, desde que você atualize o parâmetro de prefixo do publicador ao chamar pac pcf push abaixo. Mais informações: criar um editor de soluções.
Depois de salvar o publisher, você estará pronto para autorizar a CLI no seu ambiente e assim possamos enviar o componente de código compilado. Na linha de comando, use:
pac auth create --url https://myorg.crm.dynamics.comSubstitua
myorg.crm.dynamics.compela URL do seu próprio ambiente do Dataverse. Entre como um usuário administrador/personalizador quando solicitado. Os privilégios fornecidos por essas funções de usuário são necessários para implantar quaisquer componentes de código no Dataverse.Para implantar o componente de código, use:
pac pcf push --publisher-prefix samplesObservação
Se você receber o erro
, deverá instalar o Visual Studio 2019 para Windows & Mac ou oBuild Tools for Visual Studio 2019 , certificando-se de selecionar a carga de trabalho 'ferramentas de build .NET', conforme descrito nos pré-requisitos.Depois de concluído, esse processo terá criado uma pequena solução temporária chamada PowerAppTools_samples em seu ambiente e o
CanvasGridcomponente de código será adicionado a essa solução. Você pode mover o componente de código para sua própria solução mais tarde, se necessário. Mais informações: ALM (Gerenciamento do Ciclo de Vida do Aplicativo de Componente de Código).
Para usar componentes de código dentro de aplicativos de tela, você deve habilitar a estrutura de componentes Power Apps para aplicativos de tela no ambiente que você está usando.
a. Abra o Centro de Administração (admin.powerplatform.microsoft.com) e navegue até seu ambiente. b. Navegue até Configurações>Produto>Recursos. Certifique-se de que a estrutura de componentes do Power Apps para aplicativos de canvas esteja Ligada:
Crie um novo aplicativo Canvas usando o layout do Tablet.
No painel Inserir , selecione Obter mais componentes.
Selecione a guia Código no painel Importar componentes .
Selecione o componente
CanvasGrid.Selecione Importar. O componente de código agora aparecerá em componentes de código no painel Inserir .
Arraste o componente
CanvasGridpara a tela e associe à tabelaContactsem Microsoft Dataverse.Defina as seguintes propriedades no componente de código
CanvasGridusando o painel de propriedades.-
Realçar valor =
1- Esse é o valor questatecodetem quando o registro está inativo. -
Cor de Realce =
#FDE7E9- Essa é a cor a ser usada quando o registro está inativo. -
HighlightIndicator="statecode"- Esse é o campo com o qual comparar. Isso estará no painel Avançado na seção DATA .
-
Realçar valor =
Adicione um novo
TextInputcomponente e nomeie-otxtSearch.Atualize a
CanvasGrid.Itemspropriedade para serSearch(Contacts,txtSearch.Text,"fullname").Ao digitar na Entrada de Texto, você verá que os contatos são filtrados na tabela.
Adicione um novo rótulo de texto e defina o texto como "Nenhum registro encontrado". Posicione o rótulo acima da Grade do Canvas.
Defina a propriedade Visible do rótulo Text como
CanvasGrid1.FilteredRecordCount=0.
Isso significa que, quando não houver registros que correspondam ao txtSearch valor ou se um filtro de coluna for aplicado usando o menu de contexto que não retorna registros (por exemplo, Nome Completo não contém dados), o rótulo será exibido.
Adicione um Formulário de Exibição (do grupo de entrada no painel Inserir ).
Defina o formulário
DataSourcepara aContactstabela e adicione alguns campos de formulário.Defina a propriedade do formulário
ItemcomoCanvasGrid1.Selected.Agora você deverá ver que, quando selecionar itens na grade, o formulário exibirá o item selecionado.
Adicione uma nova Tela ao aplicativo de tela chamado
scrDetails.Copie o formulário da tela anterior e cole-o na nova tela.
Defina a
CanvasGrid1.OnSelectpropriedade como .Navigate(scrDetails)Quando você invoca a ação de seleção de linha da grade, agora você verá que o aplicativo navega para a segunda tela com o item selecionado.
Depuração após a implantação
Você pode depurar facilmente seu componente de código em execução dentro do aplicativo canvas ao abrir as Ferramentas de Desenvolvedor usando Ctrl+Shift+I.
Selecione e digite Ctrl+PGrid.tsx ou Index.ts. Em seguida, você pode definir um ponto de parada e passar passo a passo pelo seu código.
Se você precisar fazer mais alterações em seu componente, não precisará implantar a cada vez. Em vez disso, use a técnica descrita em Debug code components para criar um Fiddler AutoResponder para carregar o arquivo do sistema de arquivos local enquanto npm start watch está em execução.
O AutoResponder seria semelhante ao seguinte:
REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.CanvasGrid[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\CanvasGrid\out\controls\CanvasGrid\${folder}\${fname}
Você também precisará habilitar os filtros para adicionar o Access-Control-Allow-Origin cabeçalho. Mais informações: Debugging após a implantação em Microsoft Dataverse.
Você precisará esvaziar o cache e atualizar a sessão do navegador para que o arquivo AutoResponder seja coletado. Depois de carregado, você pode simplesmente atualizar o navegador, pois o Fiddler adicionará um cabeçalho de controle de cache ao arquivo para impedir que ele seja armazenado em cache.
Quando estiver satisfeito com as alterações, você poderá incrementar a versão do patch no manifesto e reimplantar usando o pac pcf push.
Até agora, você implantou um build de desenvolvimento, que não é otimizado e será executado mais lento no runtime. Você pode optar por implantar um build otimizado usando o pac pcf push editando o CanvasGrid.pcfproj. Abaixo do OutputPath, adicione o seguinte: <PcfBuildMode>production</PcfBuildMode>
<PropertyGroup>
<Name>CanvasGrid</Name>
<ProjectGuid>a670bba8-e0ae-49ed-8cd2-73917bace346</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
</PropertyGroup>
Artigos relacionados
Gerenciamento do ciclo de vida de aplicativos (ALM) com Microsoft Power Platform
Referência da API da estrutura de componentes do Power Apps
Criar seu primeiro componente
Depurar componentes de código