Didacticiel : Création d’un composant de jeu de données d’application canevas
Dans ce didacticiel, vous allez créer un composant de code de jeu de données d′application canevas et le déployer, l′ajouter à un écran et le tester en utilisant Visual Studio Code. Le composant de code affiche une grille de jeu de données déroulante et parcourable, qui fournit des colonnes triables et filtrables. Il permet également de surligner certaines lignes en configurant une colonne d′indicateur. Cette requête courante des créateurs d’applications peut être complexe à mettre en œuvre à l’aide de composants d’application canevas natifs. Les composants de code peuvent être écrits pour fonctionner à la fois dans des applications canevas et pilotées par modèle. Cependant, ce composant est écrit pour cibler spécifiquement l′utilisation dans les applications canevas.
Outre ces conditions, veillez également à ce que le composant de code respecte les conseils de meilleures pratiques :
- Utilisation de Microsoft Fluent UI
- Localisation des étiquettes de composant de code à la conception et à l′exécution
- Assurance que le composant de code présente la largeur et la hauteur fournies par l′écran de l′application canevas parent
- Considération pour le créateur d′applications pour personnaliser l′interface utilisateur à l′aide des propriétés d′entrée et des éléments d′application externes dans la mesure du possible
Notes
Avant de commencer, assurez-vous d’avoir installé toutes les conditions préalables.
Code
Vous pouvez télécharger l’exemple complet ici : PowerApps-Samples/component-framework/CanvasGridControl/.
Créer un projet pcfproj
Créez un dossier à utiliser pour votre composant de code. Par exemple,
C:\repos\CanvasGrid
Ouvrez Visual Studio Code, puis Fichier > Ouvrir le dossier et sélectionnez le dossier
CanvasGrid
. Si vous avez ajouté les extensions de l′Explorateur Windows lors de l′installation de Visual Studio Code, vous pouvez utiliser l′option de menu contextuel Ouvrir avec le code dans le dossier. Vous pouvez également charger n’importe quel dossier dans Visual Studio Code utilisantcode .
à l’invite de commandes si le répertoire actuel est défini sur cet emplacement.Dans un nouveau terminal PowerShell Visual Studio Code (Terminal > Nouveau terminal), utilisez la commande pac pcf init pour créer un projet de composant de code :
pac pcf init --namespace SampleNamespace --name CanvasGrid --template dataset
ou en utilisant la forme courte :
pac pcf init -ns SampleNamespace -n CanvasGrid -t dataset
Un nouveau
pcfproj
et des fichiers associés sont ajoutés au dossier actuel, dontpackages.json
qui définit les modules nécessaires. Pour installer les modules requis, utilisez npm install :npm install
Notes
Si vous affichez le message,
The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program.
, assurez-vous d′avoir installé toutes les conditions préalables, en particulier node.js (la version LTS est recommandée).
Le modèle contient un fichier index.ts
avec divers fichiers de configuration. Il s’agit du point de départ du composant de code, il contient les méthodes de cycle de vie décrites dans Implémentation des composants.
Installation de Microsoft Fluent UI
Vous allez utiliser Microsoft Fluent UI et React pour créer l′interface utilisateur, vous devez donc les installer en tant que dépendances. Utilisez les éléments suivants au terminal :
npm install react react-dom @fluentui/react
Cela ajoute les modules au fichier packages.json
et les installe dans le dossier node_modules
. Ne validez pas node_modules
dans le contrôle de source puisque tous les modules obligatoires peuvent être restaurés à l′aide de npm install
.
L’un des avantages de Microsoft Fluent UI est sa capacité à fournir une interface utilisateur cohérente et hautement accessible.
Configuration de eslint
Le modèle utilisé par pac pcf init installe le module eslint
sur le projet et le configure en ajoutant un fichier .eslintrc.json
. Eslint
exige maintenant une configuration pour les styles de codage TypeScript et React. Plus d’informations : Linting – Meilleures pratiques et conseils pour les composants de code.
Définition des propriétés du jeu de données
Le fichier CanvasGrid\ControlManifest.Input.xml
définit les métadonnées décrivant le comportement du composant de code. L′attribut control contient déjà l′espace de noms et le nom du composant.
Conseil
Vous trouverez peut-être le XML plus facile à lire en le formatant de manière à ce que les attributs apparaissent sur des lignes séparées. Trouvez et installez un outil de mise en forme XML de votre choix dans le marketplace Visual Studio Code : Rechercher des extensions de formatage xml.
Les exemples ci-dessous ont été formatés avec des attributs sur des lignes séparées pour les rendre plus faciles à lire.
Vous devez définir les enregistrements auxquels le composant de code peut être lié, en ajoutant ce qui suit dans l’élément control
, en remplaçant l’élément data-set
existant :
Les enregistrements data-set sont liés à un source de données si le composant de code est ajouté à une application canevas. L’élément property-set indique que l’utilisateur doit configurer l’une des colonnes de ce jeu de données à utiliser comme indicateur de surlignage des lignes.
Conseil
Vous pouvez spécifier plusieurs éléments de jeu de données. Cela peut être utile pour rechercher un jeu de données tout en affichant une liste d’enregistrements en utilisant un second.
Définition des propriétés d’entrée et de sortie
Outre le jeu de données, vous pouvez fournir les propriétés d’entrée suivantes :
HighlightValue
: permet au créateur d’applications de fournir une valeur à comparer avec la colonne définie commeHighlightIndicator
property-set
. Si les valeurs sont égales, la ligne doit être surlignée.HighlightColor
: permet au créateur d′applications de sélectionner la couleur à utiliser pour surligner les lignes.
Conseil
Lors de la création de composants de code à utiliser dans les applications canevas, il est conseillé de fournir les propriétés d′entrée pour le style des aspects communs des composants de code.
Outre les propriétés d’entrée, une propriété de sortie nommée FilteredRecordCount
est mise à jour (et déclenche l’événement OnChange
) si le nombre de lignes est modifié à cause d’une action de filtre appliquée dans le composant de code. Elle est utile pour afficher un message No Rows Found
dans l’application parent.
Notes
Demain, les composants de code prendront en charge les événements personnalisés pour vous permettre de définir un événement spécifique plutôt que d’utiliser l’événement OnChange
générique.
Pour définir ces trois propriétés, ajoutez ce qui suit au fichier CanvasGrid\ControlManifest.Input.xml
, sous l’élément 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"/>
Enregistrez ce fichier et, sur la ligne de commande, utilisez :
npm run build
Notes
Si vous obtenez une erreur comme celle-ci lors de l’exécution de 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
ouvrez le fichier index.ts et ajoutez ceci : // eslint-disable-next-line no-undef
, directement au-dessus de la ligne :
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
Puis, réexécutez la commande npm run build
.
Une fois le composant généré, vous voyez que :
Un fichier
CanvasGrid\generated\ManifestTypes.d.ts
généré automatiquement est ajouté au projet. Il est généré lors du processus de génération à partir deControlManifest.Input.xml
et fournit les types d’interaction avec les propriétés d’entrée/sortie.La sortie de version est ajoutée au dossier
out
.bundle.js
est le fichier JavaScript transpilé exécuté dans le navigateur etControlManifest.xml
est une version reformatée du fichierControlManifest.Input.xml
utilisée pendant le déploiement.Notes
Ne modifiez pas le contenu des dossiers
generated
etout
directement. Ils seront remplacés lors du processus de génération.
Ajout du composant de grille Fluent UI React
Si le composant de code utilise React, un seul composant racine est rendu dans la méthode updateView. Dans le dossier CanvasGrid
, ajoutez un nouveau fichier TypeScript nommé Grid.tsx
, puis ajoutez le contenu suivant :
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';
Notes
Le fichier a l’extension tsx
, c’est un fichier TypeScript qui prend en charge la syntaxe de style XML utilisée par React. Il est compilé en JavaScript standard par le processus de génération.
Notes sur la conception de la grille
Cette section inclut des commentaires sur la conception du composant Grid.tsx
.
Il s’agit d’un composant fonctionnel
Il s’agit d’un composant fonctionnel React, mais il pourrait également s’agir d’un composant de classe. Ceci dépend de votre style de codage préféré. Les composants de classe et les composants fonctionnels peuvent aussi être mélangés dans le même projet. Les composants de fonction et de classe utilisent la syntaxe de style tsx
utilisée par React. Informations complémentaires : Composants de fonction et de classe
Réduire la taille de bundle.js
Lors de l’importation des composants ChoiceGroup
Fluent UI à l’aide d’importations basées sur le chemin, au lieu de :
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
Stack
} from "@fluentui/react";
Ce code utilise :
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps,
} from '@fluentui/react/lib/DetailsList';
import { Stack } from '@fluentui/react/lib/Stack';
De cette façon, la taille du regroupement est plus petite, pour des besoins en capacité moindre et de meilleures performances d’exécution.
Une autre possibilité consisterait à utiliser Tree Shaking.
Affectation de déstructuration
Ce code :
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
utilise une affectation de déstructuration. De cette façon, vous extrayez les attributs nécessaires au rendu des propriétés, plutôt que de les préfixer avec props.
à chaque utilisation.
Ce code utilise également React.memo pour inclure dans un wrapper le composant fonctionnel afin qu′il ne soit pas rendu, sauf si les propriétés d′entrée ont changé.
Utilisation de React.useMemo
React.useMemo est utilisé en plusieurs endroits afin de garantir que le tableau d’éléments créé n’est muté que si les propriétés d’entrée options
ou configuration
changent. Il s’agit d’une bonne pratique en matière de composants de fonction qui réduit les rendus inutiles des composants enfants.
Autres éléments à noter :
DetailsList
dansStack
est inclus dans un wrapper car vous ajouterez ultérieurement un élément de pied de page avec les contrôles de pagination.- Le composant
Sticky
Fluent UI sert à inclure dans un wrapper les colonnes d’en-tête (en utilisantonRenderDetailsHeader
) afin qu’elles restent visibles lors du défilement de la grille. setKey
est passé àDetailsList
avecinitialFocusedIndex
de sorte que lorsque la page actuelle change, la position de défilement et la sélection sont réinitialisées.- La fonction
onRenderItemColumn
est utilisée pour rendre le contenu de la cellule. Elle accepte l′élément de ligne et utilise getFormattedValue pour renvoyer la valeur d′affichage de la colonne. La méthode getValue renvoie une valeur que vous pouvez utiliser pour obtenir un rendu alternatif. L’avantage de la méthodegetFormattedValue
est qu’elle contient une chaîne formatée pour les colonnes de type non-chaîne comme les dates et les recherches. - Le bloc
gridColumns
mappe la forme de l’objet des colonnes fournies par le contexte du jeu de données sur la forme attendue par les propriétés de colonnesDetailsList
. Étant donné qu’il est inclus dans un wrapper dans le hook React.useMemo, la sortie ne change que si les propriétéscolumns
ousorting
sont modifiées. Vous pouvez afficher les icônes de tri et de filtre sur les colonnes où les détails de tri et de filtrage fournis par le contexte du composant de code correspondent à la colonne mappée. Les colonnes sont triées à l′aide de la propriétécolumn.order
pour garantir qu′elles sont dans l′ordre défini par le créateur d′applications sur la grille. - Vous conservez un état interne pour
isComponentLoading
dans le composant React. En effet, lorsque l’utilisateur sélectionne les actions de tri et de filtrage, vous pouvez griser la grille comme repère visuel jusqu’à ce quesortedRecordIds
sont mis à jour et que l’état soit réinitialisé. Il existe une propriété d′entrée supplémentaire appeléeitemsLoading
qui est mappée sur la propriété dataset.loading fournie par le contexte du jeu de données. Les deux indicateurs servent à contrôler le signal de chargement visuel mis en œuvre à l′aide du composantOverlay
Fluent UI.
Mise à jour de index.ts
L’étape suivante consiste à apporter des modifications au fichier index.ts
pour qu’il corresponde aux propriétés définies dans Grid.tsx.
Ajouter des instructions d’importation et initialiser les icônes
Au niveau de l′en-tête de index.ts
, remplacez les importations existantes par ce qui suit :
import {IInputs, IOutputs} from './generated/ManifestTypes';
import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;
type DataSet = ComponentFramework.PropertyTypes.DataSet;
Notes
L’importation de initializeIcons
est obligatoire car ce code utilise le jeu d’icônes Fluent UI. Appelez initializeIcons
pour charger les icônes à l′intérieur du faisceau de test. Dans les applications canevas, elles sont déjà initialisées.
Ajout de champs à la classe CanvasGrid
Ajoutez les champs suivants à la classe CanvasGrid
:
export class CanvasGrid implements ComponentFramework.StandardControl<IInputs, IOutputs> {
/**
* Empty constructor.
*/
constructor() {
}
Mise à jour de la méthode init
Ajoutez ce qui suit à init
:
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary,
container: HTMLDivElement): void {
// Add control initialization code
}
La fonction init
est appelée lorsque le composant de code est initialisé pour la première fois sur un écran d’application. Stockez une référence aux éléments suivants :
notifyOutputChanged
: rappel si vous avez effectué un appel pour informer l’application canevas que l’une des propriétés a changé.container
: élément DOM auquel vous ajoutez l′interface utilisateur du composant de code.resources
: permet de récupérer les chaînes localisées dans la langue de l′utilisateur actuel.
context.mode.trackContainerResize(true)) est utilisé pour que updateView
soit appelé lorsque la taille du composant de code change.
Notes
Actuellement, il n′existe aucun moyen de déterminer si le composant de code s′exécute dans le faisceau de test. Vous devez détecter si l’élément control-dimensions
div
est présent en tant qu’indicateur.
Mise à jour de la méthode updateView
Ajoutez ce qui suit à updateView
:
public updateView(context: ComponentFramework.Context<IInputs>): void {
// Add code to update control view
}
Vous remarquez que :
- Vous appelez React.createElement en passant la référence au conteneur DOM reçu dans la fonction
init
. - Le composant
Grid
est défini dansGrid.tsx
et est importé en haut du fichier. allocatedWidth
etallocatedHeight
sont fournis par le contexte parent à chaque modification (par exemple, l’application redimensionne le composant de code ou vous passez en mode plein écran) car vous avez appelé trackContainerResize(true) dans la fonctioninit
.- Vous pouvez détecter lorsque de nouvelles lignes sont à afficher si le tableau updatedProperties contient la chaîne
dataset
. - Dans le faisceau de test, le tableau
updatedProperties
n’est pas rempli, vous pouvez donc utiliser l’indicateurisTestHarness
défini dans la fonctioninit
pour court-circuiter la logique qui définitsortedRecordId
etrecords
. Vous conservez une référence aux valeurs actuelles jusqu′à ce qu′elles changent, afin de ne pas les muter lorsqu′elles sont transmises au composant enfant, à moins qu′un nouveau rendu des données ne soit obligatoire. - Le composant de code conservant l′état de la page affichée, le numéro de page est réinitialisé lorsque le contexte parent réinitialise les enregistrements à la première page. Vous savez que vous êtes revenu à la première page lorsque
hasPreviousPage
est faux.
Mise à jour de la méthode destroy
Enfin, vous devez procéder à une réorganisation lorsque le composant de code est détruit :
Commencer le faisceau de test
Assurez-vous que tous les fichiers sont enregistrés et, sur le terminal, utilisez :
npm start watch
Vous devez définir la largeur et la hauteur pour afficher la grille du composant de code remplie à l′aide des 3 exemples d′enregistrement. Vous pouvez ensuite exporter un ensemble d′enregistrements dans un fichier CSV depuis Dataverse, puis le charger dans le faisceau de test en utilisant Entrées de données > Volet Enregistrements :
Voici quelques exemples de données séparées par des virgules que vous pouvez enregistrer dans un fichier .csv et utiliser :
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)
Notes
Une seule colonne s′affiche dans le faisceau de test, quelles que soient les colonnes que vous fournissez dans le fichier CSV chargé. Cela est dû au fait que le faisceau de test n′affiche property-set
que lorsqu′il en existe un défini. Si aucun property-set
n′est défini, toutes les colonnes dans le fichier CSV chargé sont remplies.
Ajouter une sélection de lignes
Même si DetailsList
Fluent UI permet de sélectionner des enregistrements par défaut, les enregistrements sélectionnés ne sont pas liés à la sortie du composant de code. Vous avez besoin des propriétés Selected
et SelectedItems
pour refléter les enregistrements choisis dans une application canevas, afin que les composants associés soient mis à jour. Dans cet exemple, vous autorisez la sélection d′un seul élément à la fois pour que la propriété SelectedItems
ne contienne qu′un enregistrement.
Mise à jour des importations Grid.tsx
Ajoutez ce qui suit aux importations dans 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';
Ajout de setSelectedRecords à GridProps
À l’interface GridProps
, dans Grid.tsx
, ajoutez ce qui suit :
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;
}
Ajout de setSelectedRecords property à Grid
Dans le composant de fonction Grid.tsx
mettez à jour la déstructuration des propriétés props
pour ajouter la nouvelle propriété setSelectedRecords
.
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
} = props;
Juste en dessous, ajoutez :
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,
});
});
Les hooks React.useCallback et useConst garantissent que ces valeurs ne mutent pas entre les rendus et ne provoquent pas un rendu inutile des composants enfants.
Le hook useForceUpdate garantit que si la sélection est mise à jour, le composant reflète le nombre de sélections mis à jour.
Ajout de selection à DetailsList
L’objet selection
créé pour maintenir l’état de la sélection est ensuite transmis au composant 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>
Définition du rappel setSelectedRecords
Vous devez définir le nouveau rappel setSelectedRecords
dans index.ts
et le transmettre au composant Grid
. Près du haut de la classe CanvasGrid
, ajoutez ce qui suit :
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;
Notes
La méthode est définie comme une fonction de flèche pour être associée à l’instance this
actuelle du composant de code.
L’appel de setSelectedRecordIds informe l’application canevas que la sélection a changé afin que les autres composants faisant référence à SelectedItems
et à Selected
soient mis à jour.
Ajout d’un nouveau rappel aux propriétés d’entrée
Enfin, ajoutez le nouveau rappel aux propriétés d′entrée du composant Grid
dans la méthode 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
);
Appel de l’événement OnSelect
Les applications canevas contiennent un modèle qui, si une galerie ou une grille contient une sélection d′éléments appelée (par exemple, en sélectionnant une icône de chevron), appelle l′événement OnSelect
. Vous pouvez implémenter ce modèle en utilisant la méthode openDatasetItem du jeu de données.
Ajout de onNavigate à l’interface GridProps
Comme précédemment, vous ajoutez une propriété de rappel supplémentaire sur le composant Grid
en ajoutant ce qui suit à l’interface GridProps
dans 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;
}
Ajout de onNavigate aux propriétés Grid
Là encore, vous devez ajouter la nouvelle propriété à la déstructuration des propriétés :
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
} = props;
Ajout de onItemInvoked à DetailsList
DetailList
a une propriété de rappel appelée onItemInvoked
à laquelle, en retour, vous transmettez votre rappel :
<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>
Ajout de la méthode onNavigate à index.ts
Ajoutez la méthode onNavigate
à index.ts
sous la méthode setSelectedRecords
:
onNavigate = (
item?: ComponentFramework.PropertyHelper.DataSetApi.EntityRecord
): void => {
if (item) {
this.context.parameters.records.openDatasetItem(item.getNamedReference());
}
};
Elle appelle la méthode openDatasetItem
sur l’enregistrement du jeu de données pour que le composant de code appelle l’événement OnSelect
. La méthode est définie comme une fonction de flèche pour être associée à l’instance this
actuelle du composant de code.
Vous devez transmettre ce rappel dans les propriétés du composant Grid
dans la méthode 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
);
Lorsque vous enregistrez tous les fichiers, le faisceau de test se recharge. Utilisez Ctrl
+ Shift
+ I
(ou F12
) et utilisez Ouvrir le fichier (Ctrl
+ P
) à la recherche de index.ts
; vous pouvez placer un point d’arrêt dans la méthode onNavigate
. Double-cliquer sur une ligne (ou la surligner avec les touches du curseur et appuyer sur Enter
) permet d′atteindre le point d′arrêt car DetailsList
appelle le rappel onNavigate
.
La référence à _this
est liée au fait que la fonction est définie comme une fonction de flèche et qu’elle a été transpilée dans une fermeture JavaScript pour capturer l’instance de this
.
Ajout de localisation
Avant d′aller plus loin, vous devez ajouter des chaînes de ressources au composant de code pour utiliser des chaînes localisées pour les messages tels que la pagination, le tri et le filtrage. Ajoutez un nouveau fichier CanvasGrid\strings\CanvasGrid.1033.resx
et utilisez l’éditeur de ressources Visual Studio ou Visual Studio Code avec une extension pour saisir ce qui suit :
Nom | active |
---|---|
Records_Dataset_Display |
Enregistrements |
FilteredRecordCount_Disp |
Nombre d’enregistrements filtrés |
FilteredRecordCount_Desc |
Nombre d’enregistrements après filtrage |
HighlightValue_Disp |
Surligner une valeur |
HighlightValue_Desc |
Valeur pour indiquer qu’une ligne doit être surlignée |
HighlightColor_Disp |
Couleur de surlignage |
HighlightColor_Desc |
Couleur de surlignage d’une ligne utilisant |
HighlightIndicator_Disp |
Surligner un champ d’indicateur |
HighlightIndicator_Desc |
Définir le nom du champ à comparer à la valeur surlignée |
Label_Grid_Footer |
Page {0} ({1} sélectionné(es)) |
Label_SortAZ |
A-Z |
Label_SortZA |
Z-A |
Label_DoesNotContainData |
Ne contient aucune donnée |
Label_ShowFullScreen |
Afficher en plein écran |
Conseil
Il n′est pas conseillé de modifier les fichiers resx
directement. Au lieu de cela, utilisez soit l′éditeur de ressources de Visual Studio, soit une extension pour Visual Studio Code. Rechercher une extension Visual Studio Code : Effectuer une recherche dans le marketplace Visual Studio pour trouver un éditeur resx
Les données de ce fichier peuvent également être définies en ouvrant le fichier CanvasGrid.1033.resx
dans le Bloc-notes et en copiant le contenu XML ci-dessous :
<?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>
Vous avez des chaînes de ressources pour les propriétés input
/output
et dataset
et property-set
associé. Elles seront utilisées dans Power Apps Studio au moment de la conception en fonction de la langue du navigateur du créateur. Vous pouvez également ajouter des chaînes d’étiquettes qui peuvent être récupérées au moment de l’exécution en utilisant getString. Pour d’informations : Implémentation du composant d’API de localisation.
Ajoutez ce nouveau fichier de ressources au fichier ControlManifest.Input.xml
dans l’élément resources
:
Ajouter du tri et du filtrage des colonnes
Pour autoriser l′utilisateur à trier et à filtrer à l′aide des en-têtes de colonne de grille, DetailList
dans Fluent UI fournit un moyen simple d′ajouter des menus contextuels aux en-têtes de colonne.
Ajouter onSort et onFilter à GridProps
Tout d’abord, ajoutez onSort
et onFilter
à l’interface GridProps
dans Grid.tsx
pour fournir les fonctions de rappel pour le tri et le filtrage :
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;
}
Ajouter onSort, onFilter et des ressources aux propriétés
Ajoutez ensuite ces nouvelles propriétés avec la référence resources
(pour récupérer les étiquettes localisées pour le tri et le filtrage) à la déstructuration des accessoires :
export const Grid = React.memo((props: GridProps) => {
const {
records,
sortedRecordIds,
columns,
width,
height,
hasNextPage,
hasPreviousPage,
sorting,
filtering,
currentPage,
itemsLoading,
setSelectedRecords,
onNavigate,
} = props;
Importer des composants ContextualMenu
Vous devez ajouter des importations en haut de Grid.tsx
pour utiliser le composant ContextualMenu
fourni par Fluent UI. Utilisez des importations basées sur des chemins d’accès pour réduire la taille du regroupement
import { ContextualMenu, DirectionalHint, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu';
Ajouter une fonctionnalité de rendu de menu contextuel
Ajoutez maintenant la fonctionnalité de rendu du menu contextuel à Grid.tsx
juste en dessous de la ligne
const [isComponentLoading, setIsLoading] = React.useState<boolean>(false);
:
Vous remarquez que :
- L′état
contextualMenuProps
contrôle la visibilité du menu contextuel rendu à l′aide du composantContextualMenu
de Fluent UI. - Ce code fournit un filtre simple pour n’afficher que les valeurs où le champ ne contient aucune donnée. Vous pouvez l’étendre pour offrir un filtrage supplémentaire.
- Ce code utilise
resources.getString
pour afficher les étiquettes sur le menu contextuel, qui peuvent être localisées. - Le hook
React.useCallback
, similaire àReact.useMemo
, garantit que les rappels ne sont mutés que si les valeurs dépendantes changent. Cela optimise le rendu des composants enfants.
Ajouter de nouvelles fonctions de menu contextuel à la sélection de colonne et aux événements de menu contextuel
Ajouter de nouvelles fonctions de menu contextuel à la sélection de colonne et aux événements de menu contextuel Mettez à jour const gridColumns
pour ajouter les retours onColumnContextMenu
et 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]);
Ajouter un menu contextuel à la sortie rendue
Pour afficher le menu contextuel, vous devez l′ajouter à la sortie rendue. Ajoutez ce qui suit directement sous le composant DetailsList
dans la sortie renvoyée :
<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>
Ajouter les fonctions onSort et OnFilter
Maintenant que vous avez ajouté l′interface utilisateur de tri et de filtrage, ajoutez les rappels à index.ts
pour réellement effectuer le tri et le filtrage sur les enregistrements liés au composant de code. Ajoutez ce qui suit à index.ts
en-dessous de la fonction 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();
};
Vous remarquez que :
- Le tri et le filtre sont appliqués au jeu de données en utilisant les propriétés sorting et filtering.
- Lors de la modification des colonnes de tri, les définitions de tri existantes doivent être supprimées à l’aide de la propriété plutôt que de remplacer le tableau de tri.
- Il faut appeler Actualiser après l’application du tri et du filtrage. Si un filtre et un tri sont appliqués en même temps, l′actualisation ne doit être appelée qu′une seule fois.
Ajouter des rappels OnSort et OnFilter au rendu de la grille
Enfin, vous pouvez transmettre ces deux rappels dans l′appel de rendu 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
);
Notes
À ce stade, le faisceau de test ne peut plus être utilisé car il ne prend pas en charge le tri et le filtrage. Plus tard, vous pouvez déployer en utilisant pac pcf push, puis ajouter à une application canevas pour les tests. Si vous le souhaitez, vous pouvez passer à cette étape pour voir à quoi ressemble le composant de code dans les applications canevas.
Mettre à jour la propriété de sortie FilteredRecordCount
La grille pouvant maintenant filtrer les enregistrements en interne, il est important de signaler à l′application canevas le nombre d′enregistrements affichés. Ainsi, vous pouvez afficher un message de type Aucun enregistrement.
Conseil
Vous pouvez implémenter cela en interne dans le composant de code. Toutefois, il est conseillé de laisser un maximum d′interface utilisateur à l′application canevas pour offrir plus de flexibilité au créateur d′applications.
Vous avez déjà défini une propriété de sortie appelée FilteredRecordCount
dans ControlManifest.Input.xml
. Lorsque le filtrage a lieu et que les enregistrements filtrés sont chargés, la fonction updateView
est appelée avec la chaîne dataset
dans le tableau updatedProperties. Si le nombre d′enregistrements a changé, vous devez appeler notifyOutputChanged
pour que l′application canevas sache qu′elle doit mettre à jour les contrôles qui utilisent la propriété FilteredRecordCount
. Dans la méthode updateView
de index.ts
, ajoutez ce qui suit juste au-dessus de ReactDOM.render
et au-dessous de allocatedHeight
:
const allocatedHeight = parseInt(
context.mode.allocatedHeight as unknown as string
);
Ajouter FilteredRecordCount à getOutputs
Ainsi, filteredRecordCount
est mis à jour sur le composant de code défini précédemment s′il est différent des nouvelles données reçues. Après l’appel de notifyOutputChanged
, vous devez vous assurer que la valeur est renvoyée lorsque getOutputs
est appelé. Il faut donc mettre à jour la méthode getOutputs
pour qu’elle ressemble à ce qui suit :
Ajouter une pagination à la grille
Pour les grands jeux de données, les applications canevas fractionnent les enregistrements en plusieurs pages. Vous pouvez ajouter un pied de page qui affiche les contrôles de navigation de page. Chaque bouton sera rendu à l’aide d’un IconButton
Fluent UI, que vous devez importer.
Ajouter IconButton aux importations
Ajoutez ceci aux importations dans Grid.tsx
:
import { IconButton } from '@fluentui/react/lib/Button';
Ajouter la fonction stringFormat
L’étape suivante consiste à ajouter des fonctionnalités pour charger le format pour l’étiquette de l’indicateur de page à partir des chaînes de ressources ("Page {0} ({1} Selected)"
) et les mettre en former à l’aide d’une fonction stringFormat
simple. Cette fonction pourrait être également dans un fichier distinct et partagé entre les composants, si c′est plus pratique :
Dans ce didacticiel, ajoutez-la en haut de Grid.tsx
, directement en dessous de type DataSet ...
.
function stringFormat(template: string, ...args: string[]): string {
for (const k in args) {
template = template.replace("{" + k + "}", args[k]);
}
return template;
}
Ajouter des boutons de pagination
Dans Grid.tsx
, ajoutez Stack.Item
sous l′élément Stack.Item
existant qui contient 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>
);
Vous remarquez que :
Stack
garantit que le pied de page s’empile sousDetailsList
. L′attributgrow
sert à garantir que la grille se développe pour occuper l′espace disponible.- Vous chargez le format pour l’étiquette de l’indicateur de page à partir des chaînes de ressources (
"Page {0} ({1} Selected)"
) et le mettez en forme à l’aide de la fonctionstringFormat
que vous avez ajoutée à l’étape précédente. - Fournissez le texte
alt
pour l’accessibilité sur la paginationIconButtons
. - Le style du pied de page peut tout aussi bien être appliqué à l’aide d’un nom de classe CSS faisant référence à un fichier CSS ajouté au composant de code.
Ajouter des propriétés de rappel pour prendre en charge la pagination
Ajoutez ensuite les propriétés de rappel manquantes loadFirstPage
, loadNextPage
et loadPreviousPage
.
Dans l’interface GridProps
, ajoutez ce qui suit :
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;
}
Ajouter de nouvelles propriétés de pagination à la grille
Ajoutez ces nouvelles propriétés à la déstructuration des propriétés :
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;
Ajouter des rappels à index.ts
Ajoutez ces rappels à index.ts
sous la méthode 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);
};
Mettez ensuite à jour l’appel de rendu de Grid
pour qu’il inclue ces rappels :
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
);
Ajouter la prise en charge du plein écran
Les composants de code peuvent être affichés en mode plein écran. Ceci est particulièrement utile sur les écrans de petite taille ou si l′espace est limité pour le composant de code dans un écran d′application canevas.
Importer un composant Link Fluent UI
Pour lancer le mode plein écran, vous pouvez utiliser le composant Link
de Fluent UI. Ajoutez-le aux importations en haut de Grid.tsx
:
import { Link } from '@fluentui/react/lib/Link';
Ajouter le contrôle Link
Pour ajouter un lien plein écran, ajoutez ce qui suit à Stack
existant qui contient les commandes de pagination.
Notes
Assurez-vous d′ajouter ceci au fichier imbriqué Stack
, et non au Stack
racine.
<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>
Vous remarquez que :
- Ce code utilise des ressources pour afficher l′étiquette afin de prendre en charge la localisation.
- Si le mode plein écran est ouvert, le lien ne s’affiche. À la place, le contexte de l’application parent affiche automatiquement une icône de fermeture.
Ajouter des propriétés pour prendre en charge le plein écran à GridProps
Ajoutez les propriétés onFullScreen
et isFullScreen
à l’interface GridProps
dans Grid.tsx
pour fournir les fonctions de rappel pour le tri et le filtrage :
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;
}
Ajouter des propriétés à la grille pour prendre en charge le plein écran
Ajoutez ces nouvelles propriétés à la déstructuration des propriétés :
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;
Mettre à jour index.ts pour prendre en charge le plein écran dans la grille
Pour fournir ces nouvelles propriétés, dans index.ts
, ajoutez la méthode de rappel suivante sous loadPreviousPage
:
onFullScreen = (): void => {
this.context.mode.setFullScreen(true);
};
L′appel de setFullScreen provoque l′ouverture en mode plein écran du composant de code et ajuste allocatedHeight
et allocatedWidth
en conséquence, en raison de l’appel à trackContainerResize(true)
dans la méthode init
. Une fois le mode plein écran ouvert, updateView
est appelé, ce qui met à jour le rendu du composant avec la nouvelle taille. updatedProperties
contient fullscreen_open
ou fullscreen_close
selon la transition qui se produit.
Pour stocker l′état du mode plein écran, ajoutez un nouveau champ isFullScreen
à la classe CanvasGrid
dans 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;
Modifier updateView pour suivre l’état
Ajoutez ce qui suit à la méthode updateView
pour suivre l’état :
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;
}
Transmettre le rappel et le champ isFullScreen à afficher dans la grille
Vous pouvez maintenant transmettre le rappel et le champ isFullScreen
dans les propriétés de rendu 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
);
Surlignage des lignes
Vous êtes maintenant prêt à ajouter la fonctionnalité de surlignage conditionnel des lignes. Vous avez déjà défini les propriétés d′entrée HighlightValue
et HighlightColor
, et HighlightIndicator
property-set
. property-set
permet au créateur de choisir un champ à utiliser pour comparer avec la valeur fournie dans HighlightValue
.
Importer des types pour prendre en charge le surlignage
Le rendu de ligne personnalisé dans DetailsList
nécessite quelques importations supplémentaires. Il existe déjà certains types de @fluentui/react/lib/DetailsList
, alors ajoutez IDetailsListProps
, IDetailsRowStyles
et DetailsRow
à cette déclaration d’importation dans Grid.tsx
:
import {
DetailsList,
ConstrainMode,
DetailsListLayoutMode,
IColumn,
IDetailsHeaderProps
} from '@fluentui/react/lib/DetailsList';
Créez maintenant le rendu de ligne personnalisé en ajoutant ce qui suit sous le bloc 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;
};
Vous remarquez que :
- Vous pouvez récupérer la valeur du champ choisi par le créateur par l′alias
HighlightIndicator
en utilisant :
item?.getValue('HighlightIndicator')
. - Lorsque que la valeur du champ
HighlightIndicator
correspond à la valeur du champhighlightValue
fournie par la propriété d′entrée sur le composant de code, vous pouvez ajouter une couleur d′arrière-plan à la ligne. - Le composant
DetailsRow
est utilisé parDetailsList
pour afficher les colonnes définies. Vous n’avez pas besoin de modifier le comportement autre que la couleur d’arrière-plan.
Ajouter des propriétés supplémentaires pour prendre en charge le surlignage
Ajoutez des propriétés supplémentaires pour highlightColor
et highlightValue
qui seront fournies par le rendu dans updateView
. Elles sont déjà ajoutées à l′interface GridProps
, vous n′avez donc plus qu′à les ajouter à la déstructuration des propriétés :
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;
Ajouter la méthode onRenderRow à DetailsList
Transmettez la méthode onRenderRow
dans les propriétés de 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>
Déployer et configurer le composant
Maintenant que vous avez implémenté toutes les fonctionnalités, vous devez déployer le composant de code sur Microsoft Dataverse pour le tester.
Dans l′environnement Dataverse, vérifiez qu′un éditeur est créé avec un préfixe
samples
:Il peut aussi s’agir de votre propre éditeur, à condition que vous mettiez à jour le paramètre de préfixe de l’éditeur dans l’appel de pac pcf push ci-dessous. Plus d’informations : Créer un éditeur de solutions.
Une fois l′éditeur enregistré, vous êtes prêt à autoriser la CLI sur votre environnement pour pousser le composant de code compilé. Sur la ligne de commande, entrez :
pac auth create --url https://myorg.crm.dynamics.com
Remplacez
myorg.crm.dynamics.com
par l’URL de votre environnement Dataverse. Connectez-vous en tant qu’utilisateur Administrateur lorsque vous y êtes invité. Les privilèges fournis par ces rôles d’utilisateur sont nécessaires pour déployer des composants de code sur Dataverse.Pour déployer votre composant de code, utilisez :
pac pcf push --publisher-prefix samples
Notes
Si vous affichez l’erreur,
Missing required tool: MSBuild.exe/dotnet.exe. Please add MSBuild.exe/dotnet.exe in Path environment variable or use 'Developer Command Prompt for VS
, vous devez installer soit Visual Studio 2019 pour Windows et Mac, soit Build Tools pour Visual Studio 2019, en veillant à sélectionner la charge de travail .NET Build Tools, comme cela est expliqué dans les conditions préalables.Une fois terminé, ce processus crée une petite solution temporaire nommée PowerAppTools_samples dans votre environnement et le composant de code
CanvasGrid
est ajouté à cette solution. Vous pourrez déplacer le composant de code dans votre solution ultérieurement si nécessaire. Plus d’informations : Gestion du cycle de vie des applications de composants de code (ALM).Pour utiliser des composants de code dans les applications canevas, activez Power Apps component framework pour les applications canevas dans l′environnement que vous utilisez.
a. Ouvrez le Centre d’administration (admin.powerplatform.microsoft.com) et accédez à votre environnement. b. Accédez à Paramètres > Produit > Fonctionnalités . Vérifiez que l’option Power Apps component framework pour les applications canevas est définie sur Activé :
Créez une application canevas dans la disposition Tablette.
Dans le volet Insérer, sélectionnez Obtenir plus de composants.
Sélectionnez l’onglet Code dans le volet Importer les composants.
Sélectionnez le composant
CanvasGrid
.Cliquez sur Importer. Le composant de code apparaît maintenant sous Composants de code dans le volet Insérer.
Faites glisser le composant
CanvasGrid
sur l’écran et associez-le à la tableContacts
dans Microsoft Dataverse.Définissez les propriétés suivantes sur le composant de code
CanvasGrid
à l’aide du volet des propriétés :- Valeur de surlignage =
1
: valeur affichée au niveau destatecode
si l’enregistrement est inactif. - Couleur de surlignage =
#FDE7E9
: couleur à utiliser si l’enregistrement est inactif. HighlightIndicator
="statecode"
: champ à comparer. Il apparaîtra dans le volet Avancé de la section DATA.
- Valeur de surlignage =
Ajoutez un nouveau composant
TextInput
et nommez-letxtSearch
.Mettez à jour la propriété
CanvasGrid.Items
surSearch(Contacts,txtSearch.Text,"fullname")
.À mesure que vous tapez dans Saisie de texte, vous constatez que les contacts sont filtrés dans la grille.
Ajouter une nouvelle étiquette de texte et définissez le texte sur Aucun enregistrement trouvé. Placez l’étiquette au-dessus de la grille du canevas.
Définissez la propriété Visible de l’étiquette de texte sur
CanvasGrid1.FilteredRecordCount=0
.
Autrement dit, si aucun enregistrement ne correspond à la valeur txtSearch
ou si un filtre de colonne est appliqué à l′aide du menu contextuel qui ne renvoie aucun enregistrement (par exemple, Nom complet ne contient aucune donnée), l′étiquette s′affiche.
Ajoutez un formulaire d’affichage (du groupe Entrée dans le volet Insérer).
Définissez le formulaire
DataSource
sur la tableContacts
et ajoutez des champs de formulaire.Définissez la propriété
Item
du formulaire surCanvasGrid1.Selected
.Observez maintenant comment, si vous sélectionnez des éléments sur la grille, le formulaire affiche les éléments sélectionnés.
Ajouter un nouvel écran à l’application canevas appelé
scrDetails
.Copiez le formulaire de l’écran précédent et collez-le sur le nouvel écran.
Définissez la propriété
CanvasGrid1.OnSelect
surNavigate(scrDetails)
.Si vous appelez l′action de sélection de ligne de grille, vous devez maintenant constater que l′application accède au deuxième écran avec l′élément sélectionné.
Débogage après le déploiement
Vous pouvez facilement déboguer le composant de code pendant qu′il s′exécute dans l′application canevas en ouvrant les outils de développement à l′aide de Ctrl+Shift+I
.
Sélectionnez Ctrl+P
et tapez Grid.tsx
ou Index.ts
. Vous pouvez ensuite définir un point d’arrêt et parcourir votre code.
Si vous devez apporter d′autres modifications au composant, vous n′avez pas besoin de le déployer à chaque fois. Utilisez plutôt la technique décrite dans Déboguer les composants de code pour créer une règle AutoResponder Fiddler pour charger le fichier à partir du système de fichiers local pendant l′exécution de npm start watch
.
La règle AutoResponder ressemblerait à ce qui suit :
REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.CanvasGrid[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\CanvasGrid\out\controls\CanvasGrid\${folder}\${fname}
Vous devez également activer les filtres pour ajouter l′en-tête Access-Control-Allow-Origin
. Plus d’informations : Débogage après le déploiement dans Microsoft Dataverse.
Vous devez utiliser l′option Vider le cache et actualiser sur votre session de navigateur pour le fichier AutoResponder à récupérer. Une fois chargé, actualisez simplement le navigateur car Fiddler ajoute un en-tête de contrôle du cache au fichier pour éviter qu’il ne soit mis en cache.
Une fois que vous êtes satisfait de vos modifications, incrémentez la version du correctif dans le manifeste, puis procédez au déploiement en utilisant pac pcf push.
À ce stade, vous avez déployé une version de développement, qui n′est pas optimisée et qui s′exécute plus lentement au moment de l′exécution. Vous pouvez choisir de déployer une version optimisée en utilisant pac pcf push en éditant CanvasGrid.pcfproj
. Sous OutputPath
, ajoutez ce qui suit : <PcfBuildMode>production</PcfBuildMode>
<PropertyGroup>
<Name>CanvasGrid</Name>
<ProjectGuid>a670bba8-e0ae-49ed-8cd2-73917bace346</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
</PropertyGroup>
Articles associés
Gestion du cycle de vie des applications (ALM) avec Microsoft Power Platform
Référence d’API Power Apps component framework
Création de votre premier composant
Déboguer des composants de code
Notes
Pouvez-vous nous indiquer vos préférences de langue pour la documentation ? Répondez à un court questionnaire. (veuillez noter que ce questionnaire est en anglais)
Le questionnaire vous prendra environ sept minutes. Aucune donnée personnelle n’est collectée (déclaration de confidentialité).