Introduction

In this article, we will explain how we can create multiple views and navigate between multiple views (quick views and card views), or render/replace views based on conditions.

ACE Navigations:

Scenario

In this example, we will call a graph API to fetch onedrive items. And, based on the result, we will create three card views. Based on the condition, we will render them.

So when we are calling any APIs there are multiple outcomes like records (number of records and no records found) and any error. So for this, we will create three card views. Based on that, we will render different card views.

In the end, our output will look like this:

For getting started with an Adaptive card extension in SPFx then refer to this.

Implementation

Open a command prompt.

Move to the path where you want to create a project.

Create a project directory using:

md folder-name

Move to the above-created directory using:

cd folder-name

Now execute the below command to create an SPFx solution:

yo @microsoft/sharepoint

It will ask some questions, as shown below:

Project Setup

After a successful installation, we can open a project in any source code tool. Here, I am using the VS code, so I will execute the command:

code .

Now we will create a service, model, and typescript and JSON files for different views. So in the end, our project structure will look like this (here my solution name is graphApiAce):

Project Structure

Create a models folder inside ACE and create a file called IOnedriveItems.ts.

export interface IOnedriveItems {
  value: {
    webUrl: string;
    displayName: string;
    id: string;
  }  
}

Now let’s create a service to get Onedrive Items using graph API. So for that, create the below files:

GraphService.ts

import { MSGraphClient } from "@microsoft/sp-http";
import { IOnedriveItems } from "../models/IOnedriveItems";
import { IGraphService } from "./IGraphService";

export class GraphService implements IGraphService {
    private _graphClient: MSGraphClient;
    private _GraphVersion = "v1.0";
    public init(graphClient: MSGraphClient): void {
        this._graphClient = graphClient;
    }
    public getOnedriveItems = async (): Promise < IOnedriveItems[] > => {
        if (this._graphClient === undefined) {
            throw new Error("Service is not initialized");
        }
        const apiUrl = "/me/drive/root/children";
        const itemsResponse = await this._graphClient.api(apiUrl).version(this._GraphVersion).get();
        const onedriveItems: IOnedriveItems[] = itemsResponse.value as IOnedriveItems[];
        return onedriveItems;
    }
}
export const graphService = new GraphService();

IGraphService.ts

import { MSGraphClient } from '@microsoft/sp-http';
import { IOnedriveItems } from '../models/IOnedriveItems';

export interface IGraphService {
    init(graphClient: MSGraphClient): void;
    getOnedriveItems():Promise<IOnedriveItems[]>;
}

Move to the GraphApiAceAdaptiveCardExtension.ts file.

Update states in onInit() as below,

public async onInit(): Promise < void > {
    this.state = {
        onedriveItms: undefined,
        noRecordsFoundMessage: undefined,
        errorMessage: undefined
    };
}

Move to the ./quickView folder and create the below files.

ErrorMessageCardView.ts

In this, we will use an Error icon and text. The description will be based on our requirements.

import {
    BasePrimaryTextCardView,
    IPrimaryTextCardParameters
} from '@microsoft/sp-adaptive-card-extension-base';
import {
    IGraphApiAceAdaptiveCardExtensionProps,
    IGraphApiAceAdaptiveCardExtensionState
} from '../GraphApiAceAdaptiveCardExtension';
export class ErrorMessageCardView extends BasePrimaryTextCardView < IGraphApiAceAdaptiveCardExtensionProps,
    IGraphApiAceAdaptiveCardExtensionState > {
        public get data(): IPrimaryTextCardParameters {
            return {
                title: "Error",
                primaryText: "Error while calling graph API.",
                description: this.state.errorMessage,
                iconProperty: "Error"
            };
        }
    }

NoRecordsCardView.ts

In this, we will use an Info icon and text. The description will be based on our requirements.

import {
    BasePrimaryTextCardView,
    IPrimaryTextCardParameters
} from '@microsoft/sp-adaptive-card-extension-base';
import {
    IGraphApiAceAdaptiveCardExtensionProps,
    IGraphApiAceAdaptiveCardExtensionState
} from '../GraphApiAceAdaptiveCardExtension';
export class NoRecordsView extends BasePrimaryTextCardView < IGraphApiAceAdaptiveCardExtensionProps,
    IGraphApiAceAdaptiveCardExtensionState > {
        public get data(): IPrimaryTextCardParameters {
            return {
                title: "No Records Found",
                primaryText: "Info",
                description: "Onedrive items are not available for current logged in user.",
                iconProperty: "Info"
            };
        }
    }

QuickView.ts

Update data() to set state values to render in JSON.

import { ISPFxAdaptiveCard, BaseAdaptiveCardView } from '@microsoft/sp-adaptive-card-extension-base';
import * as strings from 'GraphApiAceAdaptiveCardExtensionStrings';
import { IGraphApiAceAdaptiveCardExtensionProps, IGraphApiAceAdaptiveCardExtensionState } from '../GraphApiAceAdaptiveCardExtension';

export interface IQuickViewData {
    subTitle: string;
    title: string;
    onedriveItms: any[];
    errorMessage: string;
}
export class QuickView extends BaseAdaptiveCardView < IGraphApiAceAdaptiveCardExtensionProps,
    IGraphApiAceAdaptiveCardExtensionState,
    IQuickViewData > {
        public get data(): IQuickViewData {
            return {
                subTitle: strings.SubTitle,
                title: strings.Title,
                onedriveItms: this.state.onedriveItms,
                errorMessage: this.state.errorMessage
            };
        }
        public get template(): ISPFxAdaptiveCard {
            return require('./template/QuickViewTemplate.json');
        }
    }

Move to the ./quickView/template folder.

Update QuickViewTemplate.json

{
  "schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.2",
  "body": [
    {
      "type": "Container",
      "separator": "true",
      "$data": "${onedriveItms}",
      "items": [
        {
          "type": "TextBlock",
          "text": "${name}",
          "wrap": true
        }
      ],
      "selectAction": {
        "type": "Action.OpenUrl",
        "url": "${webUrl}"
      }
    }
  ]
}

Create ErrorMessageTemplate.json

{
  "schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.2",
  "body": [
    {
      "type": "Container",
      "separator": "true",
      "items": [
        {
          "type": "ColumnSet",
          "separator": true,
          "columns": [
            {
              "type": "Column",
              "width": "auto",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "${description}",
                  "wrap": true
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Create NoRecordsTemplate.json

{
  "schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.2",
  "body": [
    {
      "type": "Container",
      "separator": "true",
      "items": [
        {
          "type": "ColumnSet",
          "separator": true,
          "columns": [
            {
              "type": "Column",
              "width": "auto",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "No Records found",
                  "wrap": true
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Now, again, move to the GraphApiAceAdaptiveCardExtension.ts file.

1. Register views

2. Call services

3. Replace views and update states

public async onInit(): Promise < void > {
    this.state = {
        onedriveItms: undefined,
        noRecordsFoundMessage: undefined,
        errorMessage: undefined
    };
    this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView());
    this.cardNavigator.register(NO_RECORDS_CARD_VIEW_REGISTRY_ID, () => new NoRecordsView());
    this.cardNavigator.register(ERROR_MESSAGE_CARD_VIEW_REGISTRY_ID, () => new ErrorMessageCardView());
    this.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, () => new QuickView());
    const graphClient = await this.context.msGraphClientFactory.getClient();
    graphService.init(graphClient);
    setTimeout(async () => {
        await graphService.getOnedriveItems().then(items => {
            console.log("one drive items:", items);
            if (items.length) {
                this.setState({
                    onedriveItms: items
                });
            } else {
                this.cardNavigator.replace(NO_RECORDS_CARD_VIEW_REGISTRY_ID);
                return;
            }
        }).catch(error => {
            this.setState({
                errorMessage: error.code + ":" + error.message
            });
            this.cardNavigator.replace(ERROR_MESSAGE_CARD_VIEW_REGISTRY_ID);
            return;
        });
    }, 500);
    return Promise.resolve();
}

Find the full source code here.

Summary

In this article, we have explained how to manage navigations and render/replace views based on conditions with ACE in SPFx.

I hope this helps.

Sharing is caring!