Create a People Search Adaptive Card Extension
SharePoint Framework (SPFx) v1.18 introduces a new Search Card Template that can be used to implement various search scenarios.
This tutorial provides step-by-step guidance on implementing People Search with ACEs and Microsoft Graph.
Note
This tutorial assumes that you have installed the SPFx v1.18 preview version.
For more information on installing the SPFx v1.18, see SharePoint Framework v1.18 release notes.
Before you start, complete the procedures in the following articles to ensure that you understand the basic flow of creating a custom Adaptive Card Extension and using Microsoft Graph in SharePoint Framework solutions:
Scaffold an Adaptive Card Extension project
Create a new project directory for your project and change your current folder to that directory.
Create a new project by running the Yeoman SharePoint Generator from within the new directory you created.
yo @microsoft/sharepoint
When prompted, enter the following values (select the default option for all questions, which aren't mentioned):
- What is your solution name? peoplesearch-tutorial
- Which type of client-side component to create? Adaptive Card Extension
- Which template do you want to use? Search Card Template
- What is your Adaptive Card Extension name? People Search
At this point, Yeoman installs the required dependencies and scaffolds the solution files. This process might take few minutes.
Next, run gulp serve from the command line in the root of the project. Once the hosted workbench loads, you'll see the People Search card:
If you add the ACE to the workbench, you see it already prepared for mock search scenario.
If you switch to Preview mode of the workbench, you can engage with it:
type in the search box and select on the search icon to see the results Quick View mockup
Select on the Suggested item to see a single item view
Explore the scaffolded code
Explore the Card View
- Locate and open the following file in your project: ./src/adaptiveCardExtensions/peopleSearch/cardView/CardView.ts.
- The Card View implements
BaseComponentsCardView
class and implementscardViewParameters
getter to specify the card configuration
export class CardView extends BaseComponentsCardView<IPeopleSearchAdaptiveCardExtensionProps, IPeopleSearchAdaptiveCardExtensionState, ISearchCardViewParameters> {
public get cardViewParameters(): ISearchCardViewParameters {
return SearchCardView({
cardBar: {
componentName: 'cardBar',
title: this.properties.title
},
header: {
componentName: 'text',
text: strings.PrimaryText
},
body: {
componentName: 'searchBox',
placeholder: strings.Placeholder,
id: SEARCH_BOX_ID,
button: {
action: {
type: 'QuickView',
parameters: {
view: SEARCH_RESULTS_QUICK_VIEW_REGISTRY_ID
}
}
}
},
footer: {
componentName: 'searchFooter',
title: strings.Suggested,
imageInitials: 'MB',
text: strings.Title,
secondaryText: strings.SubTitle,
onSelection: {
type: 'QuickView',
parameters: {
view: ITEM_QUICK_VIEW_REGISTRY_ID
}
}
}
});
}
public get onCardSelection(): IQuickViewCardAction | IExternalLinkCardAction | undefined {
return undefined;
}
}
The body section of the Card View specifies the search box. The search button is configured to open the Quick View with the ID SEARCH_RESULTS_QUICK_VIEW_REGISTRY_ID
.
The footer section of the Card View specifies the suggested item. The suggested item is configured to open the Quick View with the ID ITEM_QUICK_VIEW_REGISTRY_ID
.
Explore the Quick Views
There are two mock Quick Views in the scaffolded code: SearchResultsQuickView
and ItemQuickView
.
The implementation of both is standard and can be found in the following files: ./src/adaptiveCardExtensions/peopleSearch/quickView/SearchResultsQuickView.ts and ./src/adaptiveCardExtensions/peopleSearch/quickView/ItemQuickView.ts.
The important part of the SearchResultsQuickView
implementation is data
getter that specifies queryString
value based on the state
of the ACE:
public get data(): ISearchResultsQuickViewData {
return {
// ...
queryString: this.state.queryString || ''
};
}
This approach allows to "share" the query string between the Card View and the Quick View. Below we show how to set this state's property from the ACE.
Explore the ACE class
The ACE class is located in the following file: ./src/adaptiveCardExtensions/peopleSearch/PeopleSearchAdaptiveCardExtension.ts and mostly has the same code as Generic Card View.
However, there's important difference: PeopleSearchAdaptiveCardExtension
overrides onBeforeAction
method to set queryString
state's value before opening a SearchResultsQuickView
.
public onBeforeAction(action: IOnBeforeActionArguments): void {
if (action.type === 'QuickView') {
//
// for the QuickView action we can get search query from the data property.
// it allows to display the same query string in the Quick View's text input.
//
const quickViewActionArguments: IQuickViewActionArguments = action as IQuickViewActionArguments;
if (quickViewActionArguments.viewId === SEARCH_RESULTS_QUICK_VIEW_REGISTRY_ID) {
this.setState({
queryString: quickViewActionArguments.data && quickViewActionArguments.data[SEARCH_BOX_ID]
});
}
}
}
With this code in place, the Quick View displays the same query string as the Card View.
Implement People Search Service
Request permissions scopes
The next step is to implement the data source for the People Search ACE. The data source is responsible for fetching the data from the Microsoft Graph and returning it to the ACE.
We use the List people endpoint to get search results and the Get a user endpoint to display a Suggested item.
For these endpoints we need to request People.Read
and User.Read
scopes respectively.
Locate and open the following file in your project: ./src/adaptiveCardExtensions/peopleSearch/config/package-solution.json.
Add webApiPermissions
property as follows
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
// ...
"webApiPermissionRequests": [{
"resource": "Microsoft Graph",
"scope": "User.Read"
}, {
"resource": "Microsoft Graph",
"scope": "People.Read"
}]
}
// ...
}
Defining Person model
- Navigate to ./src/adaptiveCardExtensions/peopleSearch and create a new folder called model with IPerson.ts file in there.
The file contains the "model" or representation of a person/user.
export interface IPerson {
id: string;
displayName: string;
jobTitle?: string;
officeLocation?: string;
picture?: string;
emailAddress?: string;
phone?: string;
}
Implement People Search Service
We use Service Locator pattern to inject the data service into the ACE. The pattern is represented by the ServiceScope class in SPFx.
Navigate to ./src/adaptiveCardExtensions/peopleSearch and create a new folder called peopleSearchService with two files in there: IPeopleSearchService.ts and PeopleSearchService.ts.
The IPeopleSearchService.ts file contains the "contract" for the service.
import { IPerson } from '../model/IPerson';
export interface IPeopleSearchService {
search: (queryString: string) => Promise<IPerson[]>;
getSuggested: () => Promise<IPerson>;
}
PeopleSearchService.ts file contains the implementation of the service with ServiceKey
static field to register the service in the Service Scope.
Before the implementation of the service, install the following dependencies from command line:
npm install @microsoft/sp-http --save --save-exact
The implementation of the service is as follows:
import { ServiceKey, ServiceScope } from '@microsoft/sp-core-library';
import { MSGraphClientFactory, MSGraphClientV3 } from '@microsoft/sp-http';
import { IPeopleSearchService } from './IPeopleSearchService';
import { IPerson } from '../model/IPerson';
/**
* type of result returned by the Microsoft Graph /people API
*/
interface IGraphPerson {
id: string;
displayName: string;
jobTitle?: string;
officeLocation?: string;
imAddress?: string;
scoredEmailAddresses?: [{
address: string;
relevanceScore: number;
}];
phones?: [{
type: string;
number: string;
}];
}
/**
* type of result returned by the Microsoft Graph /me API
*/
interface IGraphUser {
id: string;
displayName: string;
jobTitle?: string;
mail: string;
officeLocation?: string;
userPrincipalName: string;
businessPhones?: string[];
}
/**
* Converts a graph person to a person
*/
const convertGraphPersonToPerson = (graphPerson: IGraphPerson): IPerson => {
const {
id,
displayName,
jobTitle,
officeLocation,
imAddress,
scoredEmailAddresses,
phones
} = graphPerson;
return {
id,
displayName,
jobTitle: jobTitle || '',
officeLocation: officeLocation || '',
picture: `/_layouts/15/userphoto.aspx?size=S&accountname=${imAddress.replace('sip:', '')}`,
emailAddress: scoredEmailAddresses?.length ? scoredEmailAddresses[0].address : '',
phone: phones?.length ? phones[0].number : ''
};
};
/**
* Converts a graph user to a person
*/
const convertGraphUserToPerson = (graphUser: IGraphUser): IPerson => {
const {
id,
displayName,
jobTitle,
mail,
officeLocation,
userPrincipalName,
businessPhones
} = graphUser;
return {
id,
displayName,
jobTitle: jobTitle || '',
officeLocation: officeLocation || '',
picture: `/_layouts/15/userphoto.aspx?size=S&accountname=${userPrincipalName}`,
emailAddress: mail,
phone: businessPhones?.length ? businessPhones[0] : ''
};
};
export class PeopleSearchService implements IPeopleSearchService {
// Create a ServiceKey to register in the Service Scope
public static readonly serviceKey: ServiceKey<IPeopleSearchService> = ServiceKey.create<IPeopleSearchService>('PeopleSearchTutorial:PeopleSearchService', PeopleSearchService);
private _msGraphClientFactory: MSGraphClientFactory;
public constructor(serviceScope: ServiceScope) {
serviceScope.whenFinished(() => {
// Get the MSGraphClientFactory service instance from the service scope
this._msGraphClientFactory = serviceScope.consume(MSGraphClientFactory.serviceKey);
});
}
public search(queryString: string): Promise<IPerson[]> {
return this._msGraphClientFactory.getClient('3')
.then((client: MSGraphClientV3) => {
// search for people, order by display name, return persons only (no groups, etc.), return top 25 results
return client.api(`/me/people?$search="${queryString}"&orderBy=displayName&$filter=personType/class eq 'Person'&$top=25`).version('v1.0').get();
})
.then ((results: { value: IGraphPerson[] }) => {
const people: IGraphPerson[] = results.value;
return people.map((person: IGraphPerson) => {
return convertGraphPersonToPerson(person);
});
})
.catch((err) => {
console.log(err);
throw new Error('Error searching people');
});
}
public getSuggested(): Promise<IPerson> {
// we will return the current user as a suggestion for simplicity
return this._msGraphClientFactory.getClient('3')
.then((client: MSGraphClientV3) => {
return client.api('/me').version('beta').get();
})
.then((user: IGraphUser) => {
return convertGraphUserToPerson(user);
})
.catch((err) => {
console.log(err);
throw new Error('Error getting suggested person');
});
}
}
Update ACE's logic to use the service
Locate and open ./src/adaptiveCardExtensions/peopleSearch/PeopleSearchAdaptiveCardExtension.ts file.
Update the
IPeopleSearchAdaptiveCardExtensionState
to add properties for a suggestion and search results.export interface IPeopleSearchAdaptiveCardExtensionState { queryString?: string; suggested?: IPerson; results?: IPerson[]; }
Update
onInit
to request the service from the Service Scope get the suggestion to display on the card.public onInit(): Promise<void> { this.state = { }; // request suggestion this.context.serviceScope.whenFinished(() => { // get the people search service const peopleSearchService: IPeopleSearchService = this.context.serviceScope.consume(PeopleSearchService.serviceKey); // request suggestion peopleSearchService.getSuggested() .then((suggested: IPerson) => { this.setState({ suggested: suggested }); }) .catch((error: any) => { // TODO: handle error }); }); // ... }
Update the Search Results Quick View to use the service and display the results
Locate and open ./src/adaptiveCardExtensions/peopleSearch/quickView/template/SearchResultsQuickViewTemplate.json. Update it to display search results.
{ "schema": "http://adaptivecards.io/schemas/adaptive-card.json", "type": "AdaptiveCard", "version": "1.5", "body": [ { "type": "Input.Text", "id": "queryString", "inlineAction": { "type": "Action.Submit", "title": "${searchActionTitle}", "data": { "id": "search" } }, "value": "${queryString}", "placeholder": "${placeholder}" }, { "type": "TextBlock", "text": "Loading...", "size": "small", "separator": true, "spacing": "extraLarge", "$when": "${isLoading}" }, { "type": "TextBlock", "text": "We found ${numberOfResults} result(s)", "size": "small", "isSubtle": true, "separator": true, "spacing": "extraLarge", "$when": "${!isLoading}" }, { "type": "Container", "$data": "${results}", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "items": [ { "type": "Image", "style": "Person", "url": "${picture}", "size": "Small", "height": "48px", "width": "48px" } ], "width": "auto" }, { "type": "Column", "items": [ { "type": "TextBlock", "text": "${displayName}", "wrap": false, "size": "medium" }, { "type": "TextBlock", "spacing": "None", "text": "${if(length(jobTitle)!=0,jobTitle,emailAddress)}", "isSubtle": true, "wrap": false, "size": "default" } ], "width": "stretch" } ], "bleed": true, "spacing": "none" } ], "style": "default", "separator": true, "spacing": "Medium" } ] }
Locate and open ./src/adaptiveCardExtensions/peopleSearch/quickView/SearchResultsQuickView.ts.
Update the
ISearchResultsQuickViewData
interface to add properties for the search results.export interface ISearchResultsQuickViewData { searchActionTitle: string; placeholder: string; queryString: string; /** * The number of results returned by the search */ numberOfResults: number; /** * The results returned by the search */ results: IPerson[]; /** * Indicates if the search is in progress */ isLoading: boolean; }
Add
_lastQueryString
field to theSearchResultsQuickView
class to store the last processed query string.private _lastQueryString: string | undefined;
Implement
_performSearch
method to request search results from the service.private _performSearch = (queryString: string): void => { // initiate search this.context.serviceScope.consume(PeopleSearchService.serviceKey).search(queryString) .then((results: IPerson[]) => { // storing the last processed query string this._lastQueryString = queryString; this.setState({ results: results }); }) .catch(() => { // TODO: handle error }); };
Update
data
getter to initiate search if needed and return the data to display.public get data(): ISearchResultsQuickViewData { const isNewSearch: boolean = this._lastQueryString !== this.state.queryString; // initiate search if the query string has changed if (isNewSearch) { this._performSearch(this.state.queryString); } const { results } = this.state; return { searchActionTitle: strings.SearchAction, placeholder: strings.Placeholder, queryString: this.state.queryString || '', numberOfResults: isNewSearch ? 0 : results?.length, results: isNewSearch ? [] : results, isLoading: isNewSearch }; }
Run gulp serve to test the extension. Enter some text in the search box and select search icon button. You should see the following results.
Perform search for the Search Results Quick View
Locate and open ./src/adaptiveCardExtensions/peopleSearch/quickView/SearchResultsQuickView.ts. Implement
onAction
method to handleSubmit
action withid
set tosearch
:public onAction(action: IActionArguments): void { if (action.type !== 'Submit' || !action.data) { return; } const { data } = action; switch (data.id) { case 'search': // update query string this.setState({ queryString: data.queryString }); break; } }
Now if you select on Search button in the Search Results Quick View, the results are refreshed.
Open Person card from the Search Results Quick View
The next step is to open the Person card (ItemQuickView
) when selecting a search result from the Search Results Quick View.
Locate and open ./src/adaptiveCardExtensions/peopleSearch/PeopleSearchAdaptiveCardExtension.ts. Update
IPeopleSearchAdaptiveCardExtensionState
to store the selected person:export interface IPeopleSearchAdaptiveCardExtensionState { queryString?: string; suggested?: IPerson; results?: IPerson[]; selectedPerson?: IPerson; }
Locate and open ./src/adaptiveCardExtensions/peopleSearch/quickView/template/SearchResultsQuickViewTemplate.json.
Add
selectAction
property to theColumnSet
(see comment in the code){ "schema": "http://adaptivecards.io/schemas/adaptive-card.json", "type": "AdaptiveCard", "version": "1.5", "body": [ { "type": "Input.Text", "id": "queryString", "inlineAction": { "type": "Action.Submit", "title": "${searchActionTitle}", "data": { "id": "search" } }, "value": "${queryString}", "placeholder": "${placeholder}" }, { "type": "TextBlock", "text": "Loading...", "size": "small", "separator": true, "spacing": "extraLarge", "$when": "${isLoading}" }, { "type": "TextBlock", "text": "We found ${numberOfResults} result(s)", "size": "small", "isSubtle": true, "separator": true, "spacing": "extraLarge", "$when": "${!isLoading}" }, { "type": "Container", "$data": "${results}", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "items": [ { "type": "Image", "style": "Person", "url": "${picture}", "size": "Small", "height": "48px", "width": "48px" } ], "width": "auto" }, { "type": "Column", "items": [ { "type": "TextBlock", "text": "${displayName}", "wrap": false, "size": "medium" }, { "type": "TextBlock", "spacing": "None", "text": "${if(length(jobTitle)!=0,jobTitle,emailAddress)}", "isSubtle": true, "wrap": false, "size": "default" } ], "width": "stretch" } ], "bleed": true, "spacing": "none", // added select action "selectAction": { "type": "Action.Submit", "data": { "id": "selectPerson", "personId": "${id}" } } } ], "style": "default", "separator": true, "spacing": "Medium" } ] }
Locate and open ./src/adaptiveCardExtensions/peopleSearch/quickView/SearchResultsQuickView.ts.
Update
onAction
method to handleselectPerson
event: setselectedPerson
state's property and open the Person card as follows:public onAction(action: IActionArguments): void { if (action.type !== 'Submit' || !action.data) { return; } const { data } = action; switch (data.id) { case 'search': // update query string this.setState({ queryString: data.queryString }); break; case 'selectPerson': { // set selected person and open the item Quick View const person: IPerson = this.state.results.filter(p => p.id === data.personId)[0]; this.setState({ selectedPerson: person }); this.quickViewNavigator.push(ITEM_QUICK_VIEW_REGISTRY_ID); break; } } }
Locate and open ./src/adaptiveCardExtensions/peopleSearch/quickView/template/ItemQuickViewTemplate.json
Update the markup to display selected person's information as follows
{ "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.5", "$data": "${$root.person}", "body": [ { "type": "Container", "style": "emphasis", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "items": [ { "type": "Image", "style": "Person", "url": "${picture}", "size": "Small", "height": "48px", "width": "48px" } ], "width": "auto" }, { "type": "Column", "items": [ { "type": "TextBlock", "text": "${displayName}", "wrap": false, "size": "medium" }, { "type": "TextBlock", "spacing": "None", "text": "${if(length(jobTitle)!=0,jobTitle,emailAddress)}", "isSubtle": true, "wrap": false, "size": "default" } ], "width": "stretch" } ], "bleed": true, "spacing": "none" } ] }, { "type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "Contact" }, { "type": "Table", "gridStyle": "default", "firstRowAsHeader": false, "showGridLines": false, "verticalCellContentAlignment": "center", "columns": [ { "width": "20px", "horizontalCellContentAlignment": "center", "verticalCellContentAlignment": "center" }, { "width": 1 } ], "rows": [ { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "Image", "url": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDQ4IDIwNDgiIGNsYXNzPSJzdmdfZGQ3OTBlZTMiIGZvY3VzYWJsZT0iZmFsc2UiPjxwYXRoIGQ9Ik0yMDQ4IDM4NHYxMjgwSDBWMzg0aDIwNDh6TTE0MyA1MTJsODgxIDQ0MSA4ODEtNDQxSDE0M3ptMTc3NyAxMDI0VjY0OGwtODk2IDQ0Ny04OTYtNDQ3djg4OGgxNzkyeiI+PC9wYXRoPjwvc3ZnPg==", "size": "Small", "width": "16px", "height": "16px" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "${emailAddress}", "weight": "default", "wrap": false } ] } ], "style": "default" }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "Image", "url": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDQ4IDIwNDgiIGNsYXNzPSJzdmdfZGQ3OTBlZTMiIGZvY3VzYWJsZT0iZmFsc2UiPjxwYXRoIGQ9Ik0xNjA3IDEyMTNxNDQgMCA4NCAxNnQ3MiA0OGwyMjAgMjIwcTMxIDMxIDQ3IDcxdDE3IDg1cTAgNDQtMTYgODR0LTQ4IDcybC0xNCAxNHEtNTQgNTQtOTkgOTZ0LTk0IDcwLTEwOSA0NC0xNDMgMTVxLTEyNSAwLTI1Ny0zOXQtMjYyLTEwOC0yNTYtMTY0LTIzNy0yMDctMjA2LTIzOC0xNjItMjU2VDM4IDc3NSAwIDUyM3EwLTgzIDE0LTE0MnQ0My0xMDggNzAtOTMgOTYtOTlsMTYtMTZxMzEtMzEgNzEtNDh0ODUtMTdxNDQgMCA4NCAxN3Q3MiA0OGwyMjAgMjIwcTMxIDMxIDQ3IDcxdDE3IDg1cTAgNDQtMTUgNzh0LTM3IDYzLTQ4IDUxLTQ5IDQ1LTM3IDQ0LTE1IDQ5cTAgMzggMjcgNjVsNTUxIDU1MXEyNyAyNyA2NSAyNyAyNiAwIDQ4LTE1dDQ1LTM3IDQ1LTQ4IDUxLTQ5IDYyLTM3IDc5LTE1em0tODMgNzA3cTcyIDAgMTIwLTEzdDg4LTM5IDc2LTY0IDg1LTg2cTI3LTI3IDI3LTY1IDAtMTgtMTQtNDJ0LTM4LTUyLTUxLTU1LTU2LTU0LTUxLTQ3LTM3LTM1cS0yNy0yNy02Ni0yNy0yNiAwLTQ4IDE1dC00NCAzNy00NSA0OC01MiA0OS02MiAzNy03OSAxNXEtNDQgMC04NC0xNnQtNzItNDhMNTcwIDkyN3EtMzEtMzEtNDctNzF0LTE3LTg1cTAtNDQgMTUtNzh0MzctNjMgNDgtNTEgNDktNDYgMzctNDQgMTUtNDhxMC0zOS0yNy02Ni0xMy0xMy0zNC0zNnQtNDctNTEtNTQtNTYtNTYtNTItNTEtMzctNDMtMTVxLTM4IDAtNjUgMjdsLTg1IDg1cS0zNyAzNy02NCA3NnQtNDAgODctMTQgMTIwcTAgMTEyIDM2IDIzMXQxMDEgMjM4IDE1MyAyMzQgMTkyIDIxOSAyMTkgMTkwIDIzNCAxNTAgMjM2IDk5IDIyNiAzNnoiPjwvcGF0aD48L3N2Zz4=", "size": "Small", "width": "16px", "height": "16px" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "${phone}", "wrap": false } ] } ], "style": "default" }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "Image", "url": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDQ4IDIwNDgiIGNsYXNzPSJzdmdfZGQ3OTBlZTMiIGZvY3VzYWJsZT0iZmFsc2UiPjxwYXRoIGQ9Ik0xMDI0IDBxODggMCAxNzAgMjN0MTUzIDY0IDEyOSAxMDAgMTAwIDEzMCA2NSAxNTMgMjMgMTcwcTAgNjktMTYgMTMxdC00OCAxMjVsLTU3NiAxMTUyTDQ0OCA4OTZxLTMxLTYyLTQ3LTEyNHQtMTctMTMycTAtODggMjMtMTcwdDY0LTE1MyAxMDAtMTI5VDcwMSA4OHQxNTMtNjUgMTcwLTIzem00NzYgODQ2di0xbDEtMXEyNS00NyAzOC05OXQxMy0xMDVxMC0xMDktNDEtMjA1dC0xMTQtMTY4LTE2OC0xMTMtMjA1LTQycS0xMDkgMC0yMDUgNDFUNjUxIDI2NyA1MzggNDM1dC00MiAyMDVxMCA1MyAxMyAxMDV0MzggOTl2MWwxIDEgNDc2IDk1MiA0NzYtOTUyem0tNDc2LTU4MnE3OCAwIDE0NiAyOXQxMjAgODEgODAgMTE5IDMwIDE0N3EwIDc4LTI5IDE0NnQtODEgMTIwLTExOSA4MC0xNDcgMzBxLTc4IDAtMTQ2LTI5dC0xMjAtODEtODAtMTE5LTMwLTE0N3EwLTc4IDI5LTE0NnQ4MS0xMjAgMTE5LTgwIDE0Ny0zMHptMCA2NDBxNTUgMCAxMDMtMjB0ODQtNTcgNTYtODQgMjEtMTAzcTAtNTUtMjAtMTAzdC01Ny04NC04NC01Ni0xMDMtMjFxLTU1IDAtMTAzIDIwdC04NCA1Ny01NiA4NC0yMSAxMDNxMCA1NSAyMCAxMDN0NTcgODQgODQgNTYgMTAzIDIxeiI+PC9wYXRoPjwvc3ZnPg==", "size": "Small", "width": "16px", "height": "16px" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "${officeLocation}", "wrap": false } ] } ], "style": "default" } ] } ] }
Locate and open ./src/adaptiveCards/peopleSearch/quickView/ItemQuickView.ts
Update
IItemQuickViewData
as followsexport interface IItemQuickViewData { person: IPerson; }
Update
data
getter to getselectedPerson
from the state as followspublic get data(): IItemQuickViewData { return { person: this.state.selectedPerson || this.state.suggested // we can open either selected Person or suggested Person }; }
Now if you select on a person in the list, you should see a Quick View of the person's details:
Display Suggested person in the Card View
Locate and open ./src/adaptiveCards/peopleSearch/cardView/CardView.ts
Update
cardViewParameters
getter to usesuggested
value from the state as followspublic get cardViewParameters(): ISearchCardViewParameters { // default value for the footer const footer: ICardSearchFooterConfiguration = { componentName: 'searchFooter', title: strings.Suggested, text: 'No suggestions found', imageInitials: 'NA' }; // if there is a suggested person, update the footer const { suggested } = this.state; if (suggested) { footer.text = suggested.displayName; footer.secondaryText = suggested.jobTitle || suggested.emailAddress; footer.imageUrl = suggested.picture; footer.imageInitials = `${suggested.givenName.charAt(0)}${suggested.surname.charAt(0)}`; footer.onSelection = { type: 'QuickView', parameters: { view: ITEM_QUICK_VIEW_REGISTRY_ID } }; } return SearchCardView({ cardBar: { componentName: 'cardBar', title: this.properties.title }, header: { componentName: 'text', text: strings.PrimaryText }, body: { componentName: 'searchBox', placeholder: strings.Placeholder, id: SEARCH_BOX_ID, button: { action: { type: 'QuickView', parameters: { view: SEARCH_RESULTS_QUICK_VIEW_REGISTRY_ID } } } }, footer: footer });
Now you see the current user in the Suggested section of the Card View. If you select on the user information, you see the Quick View of the person's details.