Skip to content
This repository was archived by the owner on Oct 23, 2021. It is now read-only.

Lab 2 ‐ Advanced Card View Functionality

ms-nichoi edited this page Jun 8, 2021 · 3 revisions

0 - Create a test list

  1. Go to your home site and create a new list named "Instruction List".
  2. Add a "Single line of text" column named "Description".

  1. Create a few items:
    • Title = Step 1, Description = Use ACEs
    • Title = Step 2, Description = ???
    • Title = Step 3, Description = SPFx 🚀 🌝
  2. Get the list id

Save this list id for the next step.

1 - Add ACE Functionality

Start with the HelloWorld ACE from Lab 1. Make the following updates in preparation for Step 2.

1.1 - Change properties

Navigate to src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts.

Update the properties interface.

export interface IHelloWorldAdaptiveCardExtensionProps {
  title: string;
  description: string;
  iconProperty: string;
  listId: string;
}

Navigate to src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.manifest.json.

Initialize the ACE with the id of the List created in the previous step.

  "preconfiguredEntries": [{
    // ...
    "properties": {
      "title": "HelloWorld",
      "description": "HelloWorld description",
      "iconProperty": "", // Default to sharepointlogo
      "listId": ""
    }
  }]

Navigate to src/adaptiveCardExtensions/helloWorld/HelloWorldPropertyPane.ts.

Update the Property Pane.

PropertyPaneTextField('listId', {
  label: 'List ID'
})

1.2 - Change state

Navigate to src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts.

Add a new interface for the List data.

export interface IListItem {
  title: string;
  description: string;
}

Update the state interface.

export interface IHelloWorldAdaptiveCardExtensionState {
  currentIndex: number;
  items: IListItem[];
}

Update the state initialization.

public onInit(): Promise<void> {
  this.state = {
    currentIndex: 0,
    items: []
  };
  // ...
}

Temporarily remove where state is referenced in the AdaptiveCardExtension and Views.

// tslint:disable-next-line: no-any
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {

}

Navigate to src/adaptiveCardExtensions/helloWorld/quickView/QuickView.ts.

public get data(): IQuickViewData {
  return {
    subTitle: '',
    title: strings.Title
  };
}

public onAction(action: IActionArguments): void {

}

1.3 - Add dependency

Navigate to package.json.

Add @microsoft/sp-http dependency.

"dependencies": {
  // ...
  "@microsoft/sp-http": "1.13.0-beta.N" // N = the current version of the beta packages installed
},
npm install

1.4 - Fetch the list data

Navigate to src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts.

Fetch the list data using SPHttpClient.

import { SPHttpClient } from '@microsoft/sp-http';
  private _fetchData(): Promise<void> {
    if (this.properties.listId) {
      return this.context.spHttpClient.get(
        `${this.context.pageContext.web.absoluteUrl}` +
          `/_api/web/lists/GetById(id='${this.properties.listId}')/items`,
        SPHttpClient.configurations.v1
      )
        .then((response) => response.json())
        .then((jsonResponse) => jsonResponse.value.map(
          (item) => { return { title: item.Title, description: item.Description }; })
         )
        .then((items) => this.setState({ items }));
    }

    return Promise.resolve();
  }

Fetch the list data during initialization.

public onInit(): Promise<void> {
  // ...
  return this._fetchData();
}

Fetch the list data when the Property Pane is updated.

Only update the ACE, when the List ID has changed.

  protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
    if (propertyPath === 'listId' && newValue !== oldValue) {
      if (newValue) {
        this._fetchData();
      } else {
        this.setState({ items: [] });
      }
    }
  }

2 - Card updates

Navigate to src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts.

Update the data getter to display data from the list.

public get data(): IPrimaryTextCardParameters {
  const { title, description } = this.state.items[this.state.currentIndex];
  return {
    description,
    primaryText: title
  };
}

Build and launch the ACE in the hosted Workbench.

gulp serve -l --nobrowser

Once the code is being served, navigate to the hosted Workbench.

https://{tenant}.sharepoint.com/_layouts/15/workbench.aspx

Open the Toolbox and select your ACE.

3 - Conditional Card Views

By default, Views are automatically responsive to the Card size. However, ACEs can optionally provide different Views for any given Card size.

Change the HelloWorld ACE to display the total count of List items in the Medium Card size, and display the List items in the Large Card size to maximize the use of available space.

3.1 - Medium Card View

Create a new file under the src/adaptiveCardExtensions/helloWorld/cardView folder named MediumCardView.ts.

import {
  BaseBasicCardView,
  IActionArguments,
  IBasicCardParameters,
  ICardButton
} from '@microsoft/sp-adaptive-card-extension-base';
import { 
  IListItem, QUICK_VIEW_REGISTRY_ID, 
  IHelloWorldAdaptiveCardExtensionProps, 
  IHelloWorldAdaptiveCardExtensionState 
} from '../HelloWorldAdaptiveCardExtension';

// Extend from BaseBasicCardView
export class MediumCardView extends BaseBasicCardView<IHelloWorldAdaptiveCardExtensionProps, IHelloWorldAdaptiveCardExtensionState> {
  // Use the Card button to open the Quick View
  public get cardButtons(): [ICardButton] {
    return [
      {
        title: 'View All',
        action: {
          type: 'QuickView',
          parameters: {
            view: QUICK_VIEW_REGISTRY_ID
          }    
        }
      }
    ];
  }

  // Display the total number of steps
  public get data(): IBasicCardParameters {
    return {
      primaryText: `${this.state.items.length} Steps`
    };
  }
}

Navigate to src/adaptiveCardExtensions/helloWorld/HelloWorldAdaptiveCardExtension.ts.

Register the new View.

import { MediumCardView } from './cardView/MediumCardView';
const MEDIUM_VIEW_REGISTRY_ID: string = 'HelloWorld_MEDIUM_VIEW';
public onInit(): Promise<void> {
  // ...
  this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView());
  this.cardNavigator.register(MEDIUM_VIEW_REGISTRY_ID, () => new MediumCardView());
  // ...
}

Return either the Medium Card View or the Large Card View based on the Card size.

protected renderCard(): string | undefined {
  return this.cardSize === 'Medium' ? MEDIUM_VIEW_REGISTRY_ID : CARD_VIEW_REGISTRY_ID;
}

Reload the Workbench.

IMPORTANT NOTE: There is a known bug where the renderCard method is not being called again when the Card size is changed. We will fix this. The workaround is to change the Card size and refresh.

Change the Card size to Large. Refresh.

4 - Large Card interactivity

ACE Card views can be interacted with. The buttons could invoke REST APIs or be used to interact with the Card. Change the Large Card view to iterate through the List items.

4.2 - Previous / Next buttons

Navigate to src/adaptiveCardExtensions/helloWorld/cardView/CardView.ts.

The buttons on the Card view can be dynamic based on the current state of the ACE.

public get cardButtons(): [ICardButton] | [ICardButton, ICardButton] {
  const buttons: ICardButton[] = [];

  // Hide the Previous button if at Step 1
  if (this.state.currentIndex > 0) {
    buttons.push({
      title: 'Previous',
      action: {
        type: 'Submit',
        parameters: {
          id: 'previous',
          op: -1 // Decrement the index
        }
      }
    });
  }

  // Hide the Next button if at the end
  if (this.state.currentIndex < this.state.items.length - 1) {
    buttons.push({
      title: 'Next',
      action: {
        type: 'Submit',
        parameters: {
          id: 'next',
          op: 1 // Increment the index
        }
      }
    });
  }

  return buttons as [ICardButton] | [ICardButton, ICardButton];
}

Update the state when a button is clicked.

public onAction(action: IActionArguments): void {
  if (action.type === 'Submit') {
    const { id, op } = action.data;
    switch (id) {
      case 'previous':
      case 'next':
      this.setState({ currentIndex: this.state.currentIndex + op });
      break;
    }
  }
}

Reload the Workbench.

Conclusion

After this lab you should be familiar with:

  • Changing the default properties of an ACE
  • Changing the ACE properties/state interfaces
  • Creating and registering Card views
  • Conditionally rendering Card view elements
  • Advanced Card view manipulation

Clone this wiki locally