Permalink
Newer
100644
534 lines (383 sloc)
20.4 KB
1
---
2
title: Build your first SharePoint Adaptive Card Extension
3
description: Adaptive Card Extensions (ACEs) are a new SharePoint Framework component type, which enable developers to build rich, native extensions to Viva Connections' Dashboards and SharePoint Pages. In this tutorial, you'll build and explore your first ACE.
6
---
7
# Build your first SharePoint Adaptive Card Extension
8
9
Adaptive Card Extensions (ACEs) are a new SharePoint Framework component type, which enables developers to build rich, native extensions to Viva Connections' Dashboards and SharePoint Pages. Since Adaptive Card Extensions use Microsoft's Adaptive Card Framework to generate UI with its declarative JSON schema, you only need to focus on your component's business logic and let the SharePoint Framework (SPFx) handle making your component look good and work across all platforms.
10
11
> [!IMPORTANT]
12
> This tutorial assumes you have installed the SPFx v1.13. For more information on installing the SPFx v1.13, see [SharePoint Framework v1.13 release notes](../../release-1.13.md).
14
## Scaffold an Adaptive Card Extension project
15
16
Create a new project directory for your project and change your current folder to that directory.
18
Create a new project by running the Yeoman SharePoint Generator from within the new directory you created:
22
```
23
24
When prompted, enter the following values (*select the default option for all prompts omitted below*):
25
26
- **Do you want to allow tenant admin the choice of deploying the solution to all sites immediately without running any feature deployment or adding apps in sites?** Yes
29
- **What is your Adaptive Card Extension name?** HelloWorld
30
- **What is your Adaptive Card Extension description?** Hello World description
31
32
At this point, Yeoman installs the required dependencies and scaffolds the solution files. This process might take few minutes.
36
When you use the gulp task **serve**, by default it will launch a browser with the specified hosted workbench URL specified in your project. The default URL for the hosted workbench in a new project points to an invalid URL.
38
- Locate and open the file **./config/serve.json** in your project.
39
- Locate the property `initialPage`:
40
41
```json
42
{
43
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
44
"port": 4321,
45
"https": true,
46
"initialPage": "https://enter-your-SharePoint-site/_layouts/workbench.aspx"
47
}
48
```
50
- Change the `enter-your-SharePoint-site` domain to the URL of your SharePoint tenant and site you want to use for testing. For example: `https://contoso.sharepoint.com/sites/devsite/_layouts/workbench.aspx`.
52
> [!TIP]
53
> You can also start the local web server without launching a browser by including the `nobrowser` argument to the **gulp serve** command. For example, you may not want to modify the **serve.json** file in all your projects and instead, use a bookmark to launch your hosted workbench.
54
>
55
> ```console
56
> gulp serve --nobrowser
57
> ```
58
59
## Serve the ACE in the workbench
60
61
Before digging into the code, run the scaffolded output and see what an Adaptive Card Extension looks like.
62
63
The inner development loop with ACEs is similar to SPFx Web Parts. We can serve locally and run the code on the workbench.
64
65
```console
66
gulp serve
67
```
69
Once local webserver is running, navigate to the hosted Workbench: `https://{tenant}.sharepoint.com/_layouts/15/workbench.aspx`
72
73
:::image type="content" source="../../../images/viva-extensibility/lab1-toolbox.png" alt-text="Select the ACE from the toolbox":::
74
75
### Explore the Card View
76
77
ACEs can render in two distinct ways. The first way an ACE can render is called the **Card view**.
78
79
When rendered on a Dashboard or a Page, ACEs will always start in this view.
80
81
:::image type="content" source="../../../images/viva-extensibility/lab1-hw-ace.png" alt-text="ACE rendered in Card View":::
82
83
### Explore the Quick View
84
85
The second way an ACE can render is called the **Quick View**. When you interact with an ACE, ACEs can launch a larger, customized experience.
86
87
> [!NOTE]
88
> ACE interaction is disabled while in **Edit** mode. The Workbench or Page must be in *Preview* or *Read* mode to interact with the ACE.
89
91
92
:::image type="content" source="../../../images/viva-extensibility/lab1-preview.png" alt-text="Set the workbench to preview mode":::
93
95
96
:::image type="content" source="../../../images/viva-extensibility/lab1-hw-ql.png" alt-text="Select the Quick View button on the ACE":::
97
99
100
### Explore the base class
101
102
Locate and open the following file in your project: **./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts**.
103
104
```typescript
105
export default class HelloWorldAdaptiveCardExtension
106
extends BaseAdaptiveCardExtension<IHelloWorldAdaptiveCardExtensionProps,IHelloWorldAdaptiveCardExtensionState> {
107
// ...
108
}
109
```
110
111
All ACEs must extend from the `BaseAdaptiveCardExtension` class. You can optionally implement two generics:
112
113
- **TProperties**: Similar to Web Parts, this is the set of persisted properties of the component (*property bag*).
114
- **TState**: Unique to ACEs and can *optionally* define the set of renderable data.
115
116
### Rendering the ACE
117
118
```typescript
119
protected renderCard(): string | undefined {
120
return CARD_VIEW_REGISTRY_ID;
121
}
122
```
123
124
The `renderCard()` method is `virtual` that returns a string identifier to a registered View; more on View registration later. This method is invoked during the **initial** render of the Card view.
125
126
If `renderCard()` isn't overridden, then a default Card view will be rendered.
127
128
Comment out the `renderCard()` method and see what happens:
129
130
```typescript
131
/*
132
protected renderCard(): string | undefined {
133
return CARD_VIEW_REGISTRY_ID;
134
}
135
*/
136
```
137
138
:::image type="content" source="../../../images/viva-extensibility/lab1-default.png" alt-text="Results of commenting the renderCard() method":::
139
142
The default Card view will render using the following properties from the manifest:
144
- Icon: `iconProperty`
145
- Title: `title`
146
- Card text: `description`
153
For a View to be used, it must be registered with its respective **ViewNavigator**. Two **ViewNavigator**s are exposed on the ACE: `cardNavigator` and `quickViewNavigator`:
154
155
```typescript
156
this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView());
157
this.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, () => new QuickView());
158
```
159
160
> [!NOTE]
161
> You must register a view before it can be used. You can do this within the class' constructor or `onInit()` method.
162
163
### Card Views
164
165
Locate and open the file: **./src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts**.
166
167
Card views must extend from these base classes:
168
169
- `BaseBasicCardView`
170
171
:::image type="content" source="../../../images/viva-extensibility/lab1-basic-ace.png" alt-text="BaseBasicCardView":::
172
173
- `BaseImageCardView`
174
175
:::image type="content" source="../../../images/viva-extensibility/lab1-image-ace.png" alt-text="BaseImageCardView":::
176
177
- `BasePrimaryTextCardView`
178
179
:::image type="content" source="../../../images/viva-extensibility/lab1-primary-ace.png" alt-text="BasePrimaryTextCardView":::
180
181
Each of these Views will render differently and have different constraints on what data can be provided to the template.
182
183
> [!NOTE]
184
> The card views for Adaptive Card templates are fixed and cannot be changed.
185
186
Additionally, there are two generics for the `properties` and `state` objects shared between the view and the ACE.
187
188
- **TProperties**: The View's properties interface, the same interface used by persisted properties of the ACE (*property bag*).
189
- **TState**: Unique to ACEs and can *optionally* define the set of renderable data.
190
191
> [!NOTE]
192
> SPFx will automatically propagate changes to the ACE's state to each View.
193
194
The `data` getter is the only method that must be implemented by a Card view. The return type is unique to the parent class of the View.
195
196
The `cardButtons` property determines how many buttons appear on the Card and what action to do when clicked.
197
198
If `cardButtons` isn't implemented, then no buttons will appear on the Card.
199
200
> [!NOTE]
201
> Whereas the initial Card view is specified in the ACE's `renderCard()` method, the initial Quick View is specified as part of a button's action `parameters`. This allows two buttons to potentially open different views.
202
203
Add a second button by adding another object to the array returned by the `cardButtons()` method:
204
205
```typescript
206
public get cardButtons(): [ICardButton] | [ICardButton, ICardButton] | undefined {
207
return [
208
{
209
title: strings.QuickViewButton,
210
action: {
211
type: 'QuickView',
212
parameters: {
213
view: QUICK_VIEW_REGISTRY_ID
214
}
215
}
216
},
217
{
218
title: 'Bing',
219
action: {
220
type: 'ExternalLink',
221
parameters: {
222
target: 'https://www.bing.com'
223
}
224
}
225
}
226
];
227
}
228
```
229
230
Initially, there won't be any change in the Card. This is because the **Medium** Card size for
231
**BasePrimaryTextCardView** only shows one button. SPFx will select the first element in the tuple.
232
233
1. Change the Card size by going to the Property Pane and selecting **Large**.
235
:::image type="content" source="../../../images/viva-extensibility/lab1-size.png" alt-text="Select the card size":::
237
:::image type="content" source="../../../images/viva-extensibility/lab1-large.png" alt-text="Rendered large ACE card":::
239
1. Now, when you select the **Bing** button, Bing will open in a new browser tab.
240
241
The `onCardSelection()` method determines what will happen if the Card is clicked. If the `onCardSelection()` method isn't implemented, then nothing will happen when the Card is clicked.
242
243
1. Change the Card selection to open the **Quick** view by modifying the `onCardSelection()` method:
245
```typescript
246
public get onCardSelection(): IQuickViewCardAction | IExternalLinkCardAction | undefined {
247
return {
248
type: 'QuickView',
249
parameters: {
250
view: QUICK_VIEW_REGISTRY_ID
251
}
252
};
259
260
Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/quickView/QuickView.ts**.
261
262
Quick Views must extend the **BaseAdaptiveCardView** base class. There are three optional generics that can be defined:
265
- **TProperties**: Similar to the Card view, this is the same interface used by persisted properties of the ACE (*property bag*).
266
- **TState** Similar to the Card view, this is the set of stateful data the View needs to render. **TState** must share properties with the ACE's state interface.
267
268
A Quick View has more control over the Adaptive Card template schema than a Card view. The
269
`template()` getter must return valid Adaptive Card template JSON. SPFx ACEs support Adaptive Card
270
templating. The properties on the object returned from the `data` getter will automatically be mapped
271
to the bound template slot.
272
295
> You must use the Adaptive Card binding syntax that uses `$` and `{}` brackets.
299
1. Remove the `description` property from the Quick View data, and add two buttons.
300
1. Update the `IQuickViewData` interface as shown in the following code:
302
```typescript
303
export interface IQuickViewData {
304
title: string;
305
subTitle: string;
306
}
307
```
311
```typescript
312
public get data(): IQuickViewData {
313
return {
314
subTitle: this.state.subTitle,
315
title: strings.Title
316
};
317
}
318
```
320
1. Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts**.
321
1. Update the `IHelloWorldAdaptiveCardExtensionState` interface and `onInit()` method as follows:
323
```typescript
324
export interface IHelloWorldAdaptiveCardExtensionState {
325
subTitle: string;
326
}
330
public onInit(): Promise<void> {
331
this.state = {
332
subTitle: 'No button clicked'
333
};
334
// ...
335
}
336
```
338
next, remove the reference to `this.properties.description` from the Card view:
340
1. Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts**.
341
1. Remove the `description` property in the returned object:
343
```typescript
344
public get data(): IPrimaryTextCardParameters {
345
return {
346
primaryText: strings.PrimaryText
347
};
348
}
349
```
351
In its `template()` getter, the Quick View of the ACE you generated returns the object from a JSON file. Let's now modify that template:
353
1. Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/quickView/template/QuickViewTemplate.json**.
354
1. Replace the contents of this file with the following JSON:
358
"schema": "http://adaptivecards.io/schemas/adaptive-card.json",
359
"type": "AdaptiveCard",
360
"version": "1.2",
361
"body": [
362
{
363
"type": "TextBlock",
364
"weight": "Bolder",
365
"text": "${title}"
366
},
373
"type": "ActionSet",
374
"actions": [
375
{
376
"type": "Action.Submit",
377
"title": "Button One",
378
"style": "positive",
379
"data": {
380
"id": "button1",
381
"message": "Clicked Button One"
382
}
383
},
384
{
385
"type": "Action.Submit",
386
"title": "Button Two",
387
"data": {
388
"id": "button2",
389
"message": "Clicked Button Two"
390
}
391
}
392
]
396
```
397
398
Test your changes by refreshing the hosted workbench in the browser. It should pickup the changes you've applied to the project if **gulp serve** is still running:
399
400
:::image type="content" source="../../../images/viva-extensibility/lab1-new-ql.png" alt-text="Updated ACE Quick View":::
401
402
> [!TIP]
403
> Learn more about Adaptive Cards at https://adaptivecards.io. This site also includes an Adaptive Cards Designer that lets you preview the rendering and structure of adaptive cards as you create them.
404
405
At this point, you've modified your ACE to include two new buttons in the Quick View card. The next step is to implement what happens when these buttons are selected. This is done using *action handlers*.
406
407
### Action handlers
408
409
Actions are handled by the views where they're defined.
410
411
The Quick View has two buttons, but the view is currently not handling the **Submit** action. The `onAction()` method is invoked whenever an Adaptive Card Action is executed, for instance when the **Action.Submit** action is initiated.
412
413
Locate and open the file **QuickView.ts** and override the `onAction()` to handle the two button selections as shown in the following code:
414
415
```typescript
416
import { ISPFxAdaptiveCard, BaseAdaptiveCardView, IActionArguments } from '@microsoft/sp-adaptive-card-extension-base';
417
418
..
419
420
public onAction(action: IActionArguments): void {
421
if (action.type === 'Submit') {
422
const { id, message } = action.data;
423
switch (id) {
424
case 'button1':
425
case 'button2':
426
this.setState({
427
subTitle: message
435
Test your changes by refreshing the hosted workbench in the browser. It should pickup the changes you've applied to the project if **gulp serve** is still running.
436
437
Selecting either button will now set the state's `subTitle` to the `data.message` value, causing a re-render (*more on this later*). The Quick View's Adaptive Card will now display this message, since its template binds to `subTitle`.
441
Similar to web parts, ACE's can have configurable properties that are set by users with appropriate permissions. These enable you to customize each implementation of your ACE. This is done using the property pane.
443
ACEs can be configured just like Web Parts. The API signatures are identical for the following methods, found in the **HelloWorldAdaptiveCardExtension.ts** file:
444
445
- `getPropertyPaneConfiguration()`
446
- `onPropertyPaneFieldChanged()`
447
448
The default scaffolding for ACEs uses a new API that aims to minimize the bundle size when the component isn't in **Edit** mode. The `loadPropertyPaneResources()` method utilizes Webpack's chunking feature to separate the Property Pane specific code into its own JS file, which can then be loaded on demand.
449
450
In addition to returning the Property Pane configuration, the **HelloWorldPropertyPane** class is used to encapsulate all your **Edit** mode logic.
451
452
### Properties
453
454
Other than the **Card size** field, the scaffolded ACE has three (3) configurable fields, which are defined in `getPropertyPaneConfiguration()` method & defined in the **IHelloWorldAdaptiveCardExtension** interface:
460
Card views are designed to automatically work across all card sizes. Aside from specifying a default card size, ACEs cannot control this property.
462
The `title` and `iconProperty` properties, defined in the ACE file (ie: **HelloWorldAdaptiveCardExtension.ts**) are used in the ACE's `title()` and `iconProperty()` getters, respectively, to configure the card's title and icon:
463
464
The `title` value is used in the title of the Property Pane and the title displayed on the Card.
465
466
```typescript
467
public get title(): string {
468
return this.properties.title;
469
}
470
```
471
473
474
```typescript
475
protected get iconProperty(): string {
476
return this.properties.iconProperty || require('./assets/sharepointlogo.png');
477
}
478
```
479
480
### State
481
482
The `state` property must be initialized before calling the `setState()` method, and it can only be initialized once.
483
484
```typescript
485
public onInit(): Promise<void> {
486
this.state = {
487
subTitle: 'No button clicked'
488
};
489
// ...
490
}
491
```
492
493
Unlike with `properties`, `state` isn't persisted past the current session and should only be used for ephemeral View state.
494
496
> The scaffolded ACE maintains a `description` property in the `state` object. This is obsolete, since the ACE and all its views can simply reference the `description` stored in `properties`.
497
498
### Re-rendering
499
500
Re-rendering happens when a property is updated in the PropertyPane or if `setState()` is called.
502
When you update the Property Pane's **Description Field** value, it will update the description on the Card. Let's see how to do this:
504
1. Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts**.
505
1. As a trivial example, update the `subTitle` value when the `description` is updated during the `onPropertyPaneFieldChanged` event. Add the following code to the ACE **HelloWorldAdaptiveCardExtension** class:
507
```typescript
508
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
509
if (propertyPath === 'description') {
510
this.setState({
511
subTitle: newValue
512
});
513
}
514
}
515
```
517
Passing a `Partial<TState>` object to `setState()` method will update all Views with the new values. Updating the **Description Field** in the Property Pane will now update the `subTitle` displayed on the Quick View.
518
519
If no value or identical values are passed, a re-render will still occur.
520
521
The `setState()` method isn't just limited to the Property Pane. It can be used in response to receiving new data or as a result of some user action.
522
523
## Conclusion
524
525
After this tutorial you should be familiar with:
526
527
- Scaffolding an Adaptive Card Extension
528
- Registering Views