Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Permalink
Newer
Older
100644 534 lines (383 sloc) 20.4 KB
July 22, 2021 12:15
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.
4
ms.date: 10/25/2021
5
ms.localizationpriority: high
July 22, 2021 12:15
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]
September 8, 2021 14:06
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).
July 22, 2021 12:15
13
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.
July 22, 2021 12:15
17
18
Create a new project by running the Yeoman SharePoint Generator from within the new directory you created:
July 22, 2021 12:15
19
20
```console
21
yo @microsoft/sharepoint
July 22, 2021 12:15
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
July 22, 2021 12:15
27
- **Which type of client-side component to create?** Adaptive Card Extension
28
- **Which template do you want to use?** Primary Text Template
July 22, 2021 12:15
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.
July 22, 2021 12:15
33
34
### Update your project's hosted workbench URL
July 22, 2021 12:15
35
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.
July 22, 2021 12:15
37
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
```
July 22, 2021 12:15
49
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`.
July 22, 2021 12:15
51
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
```
July 22, 2021 12:15
68
69
Once local webserver is running, navigate to the hosted Workbench: `https://{tenant}.sharepoint.com/_layouts/15/workbench.aspx`
July 22, 2021 12:15
70
September 8, 2021 14:06
71
Open the **Web Part Toolbox** and select your ACE:
July 22, 2021 12:15
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.
July 22, 2021 12:15
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
90
Switch the Workbench to **Preview** mode.
July 22, 2021 12:15
91
92
:::image type="content" source="../../../images/viva-extensibility/lab1-preview.png" alt-text="Set the workbench to preview mode":::
93
94
Select the **Quick View** button on the ACE:
July 22, 2021 12:15
95
96
:::image type="content" source="../../../images/viva-extensibility/lab1-hw-ql.png" alt-text="Select the Quick View button on the ACE":::
97
98
## Examine the scaffolded code
July 22, 2021 12:15
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
September 8, 2021 14:06
106
extends BaseAdaptiveCardExtension<IHelloWorldAdaptiveCardExtensionProps,IHelloWorldAdaptiveCardExtensionState> {
July 22, 2021 12:15
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.
July 22, 2021 12:15
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
140
Uncomment the `renderCard()` method to go back to the original state.
July 22, 2021 12:15
141
142
The default Card view will render using the following properties from the manifest:
July 22, 2021 12:15
143
144
- Icon: `iconProperty`
145
- Title: `title`
146
- Card text: `description`
July 22, 2021 12:15
147
148
> [!NOTE]
149
> Unlike with the Card view, there is no default Quick View.
July 22, 2021 12:15
150
151
### Register a view for the ACE
July 22, 2021 12:15
152
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`:
July 22, 2021 12:15
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.
July 22, 2021 12:15
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.
July 22, 2021 12:15
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**.
July 22, 2021 12:15
234
235
:::image type="content" source="../../../images/viva-extensibility/lab1-size.png" alt-text="Select the card size":::
July 22, 2021 12:15
236
237
:::image type="content" source="../../../images/viva-extensibility/lab1-large.png" alt-text="Rendered large ACE card":::
July 22, 2021 12:15
238
239
1. Now, when you select the **Bing** button, Bing will open in a new browser tab.
July 22, 2021 12:15
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:
July 22, 2021 12:15
244
245
```typescript
246
public get onCardSelection(): IQuickViewCardAction | IExternalLinkCardAction | undefined {
247
return {
248
type: 'QuickView',
249
parameters: {
250
view: QUICK_VIEW_REGISTRY_ID
251
}
252
};
July 22, 2021 12:15
253
}
254
```
July 22, 2021 12:15
255
256
1. Now, when you select the Card, it will open the Quick View.
July 22, 2021 12:15
257
258
### ACE Quick Views
July 22, 2021 12:15
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:
July 22, 2021 12:15
263
264
- **TData**: The type returned from the `data()` getter method.
265
- **TProperties**: Similar to the Card view, this is the same interface used by persisted properties of the ACE (*property bag*).
July 22, 2021 12:15
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
273
For example, `${description}` is bound to `this.properties.description`.
July 22, 2021 12:15
274
275
```typescript
276
// QuickView.ts
July 22, 2021 12:15
277
public get data(): IQuickViewData {
278
return {
279
// ...
280
description: this.properties.description
July 22, 2021 12:15
281
};
282
}
283
```
284
285
```json
286
// QuickViewTemplate.json.ts
July 22, 2021 12:15
287
{
288
"type": "TextBlock",
289
"text": "${description}",
290
"wrap": true
July 22, 2021 12:15
291
}
292
```
293
294
> [!NOTE]
295
> You must use the Adaptive Card binding syntax that uses `$` and `{}` brackets.
July 22, 2021 12:15
296
297
Let's change this:
July 22, 2021 12:15
298
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:
July 22, 2021 12:15
301
302
```typescript
303
export interface IQuickViewData {
304
title: string;
305
subTitle: string;
306
}
307
```
July 22, 2021 12:15
308
309
1. Update the `data()` method as shown in the following code:
July 22, 2021 12:15
310
311
```typescript
312
public get data(): IQuickViewData {
313
return {
314
subTitle: this.state.subTitle,
315
title: strings.Title
316
};
317
}
318
```
July 22, 2021 12:15
319
320
1. Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts**.
321
1. Update the `IHelloWorldAdaptiveCardExtensionState` interface and `onInit()` method as follows:
July 22, 2021 12:15
322
323
```typescript
324
export interface IHelloWorldAdaptiveCardExtensionState {
325
subTitle: string;
326
}
July 22, 2021 12:15
327
328
..
July 22, 2021 12:15
329
330
public onInit(): Promise<void> {
331
this.state = {
332
subTitle: 'No button clicked'
333
};
334
// ...
335
}
336
```
July 22, 2021 12:15
337
338
next, remove the reference to `this.properties.description` from the Card view:
July 22, 2021 12:15
339
340
1. Locate and open the following file: **./src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts**.
341
1. Remove the `description` property in the returned object:
July 22, 2021 12:15
342
343
```typescript
344
public get data(): IPrimaryTextCardParameters {
345
return {
346
primaryText: strings.PrimaryText
347
};
348
}
349
```
July 22, 2021 12:15
350
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:
July 22, 2021 12:15
355
356
```json
July 22, 2021 12:15
357
{
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
},
July 22, 2021 12:15
367
{
368
"type": "TextBlock",
369
"text": "${subTitle}",
370
"wrap": true
July 22, 2021 12:15
371
},
372
{
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
]
July 22, 2021 12:15
393
}
394
]
395
}
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:
July 22, 2021 12:15
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
July 22, 2021 12:15
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.
July 22, 2021 12:15
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
September 24, 2021 14:05
428
});
July 22, 2021 12:15
429
break;
430
}
431
}
432
}
433
```
434
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`.
July 22, 2021 12:15
438
439
### Property Pane
440
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.
July 22, 2021 12:15
442
443
ACEs can be configured just like Web Parts. The API signatures are identical for the following methods, found in the **HelloWorldAdaptiveCardExtension.ts** file:
July 22, 2021 12:15
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:
July 22, 2021 12:15
455
456
- `title`
457
- `iconProperty`
458
- `description`
459
460
Card views are designed to automatically work across all card sizes. Aside from specifying a default card size, ACEs cannot control this property.
July 22, 2021 12:15
461
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.
July 22, 2021 12:15
465
466
```typescript
467
public get title(): string {
468
return this.properties.title;
469
}
470
```
471
472
The `iconProperty` value is the URL of the icon used by Card views.
July 22, 2021 12:15
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.
July 22, 2021 12:15
501
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:
July 22, 2021 12:15
503
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:
July 22, 2021 12:15
506
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
```
July 22, 2021 12:15
516
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.
July 22, 2021 12:15
522
523
## Conclusion
524
525
After this tutorial you should be familiar with:
526
527
- Scaffolding an Adaptive Card Extension
528
- Registering Views
529
- Changing the Card view and Quick View
July 22, 2021 12:15
530
- Basic action handling
531
- Changing the Property Pane
532
- Defer loading the Property Pane
533
- How to use `state`
534
- Difference between `properties` and `state`