PnP React Controls Part 4 – Listview Control

Introduction

Hi friends, this is part 4 where I will explain the usage of the Listview control. We will see how we can leverage this control on our SPFx projects and make use of the feature provided by this control. Thanks to the community.

About the Control

This control allows us to render the contents in the list view or grid view with lot of customizable properties. By using this control we can build the grid view like same as the OOB which is seen in the lists page. The control internally uses the Details list from Office UI Fabric, by providing the customizable properties, lot of boiler plate codes are reduced.

Focus on the Code

You can refer Part 1 post on the creation of the SPFx project and installing all the dependencies. In addition to the dependencies mentioned in that post you have to install @pnp/sp – v3.5.1. You should also do some changes to the project files to make the pnpjs version 3 to work on SPFx 1.14.0. Please follow this link to do the changes.

Let us see the file webpart.ts

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
    IPropertyPaneConfiguration,
    PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import { SPFI, spfi, SPFx } from "@pnp/sp";
import * as strings from 'ListViewDemoWebPartStrings';
import ListViewDemo from './components/ListViewDemo';
import { IListViewDemoProps } from './components/IListViewDemoProps';

export interface IListViewDemoWebPartProps {
    description: string;
}

export default class ListViewDemoWebPart extends BaseClientSideWebPart<IListViewDemoWebPartProps> {

    private _isDarkTheme: boolean = false;
    private _environmentMessage: string = '';
    private sp: SPFI = null;

    protected onInit(): Promise<void> {
        this._environmentMessage = this._getEnvironmentMessage();
        this.sp = spfi().using(SPFx(this.context));
        return super.onInit();
    }

    public render(): void {
        const element: React.ReactElement<IListViewDemoProps> = React.createElement(
            ListViewDemo,
            {
                description: this.properties.description,
                isDarkTheme: this._isDarkTheme,
                environmentMessage: this._environmentMessage,
                hasTeamsContext: !!this.context.sdks.microsoftTeams,
                userDisplayName: this.context.pageContext.user.displayName,
                sp: this.sp
            }
        );

        ReactDom.render(element, this.domElement);
    }

    private _getEnvironmentMessage(): string {
        if (!!this.context.sdks.microsoftTeams) { // running in Teams
            return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
        }

        return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment;
    }

    protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
        if (!currentTheme) {
            return;
        }

        this._isDarkTheme = !!currentTheme.isInverted;
        const {
            semanticColors
        } = currentTheme;
        this.domElement.style.setProperty('--bodyText', semanticColors.bodyText);
        this.domElement.style.setProperty('--link', semanticColors.link);
        this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered);

    }

    protected onDispose(): void {
        ReactDom.unmountComponentAtNode(this.domElement);
    }

    protected get dataVersion(): Version {
        return Version.parse('1.0');
    }

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
            pages: [
                {
                    header: {
                        description: strings.PropertyPaneDescription
                    },
                    groups: [
                        {
                            groupName: strings.BasicGroupName,
                            groupFields: [
                                PropertyPaneTextField('description', {
                                    label: strings.DescriptionFieldLabel
                                })
                            ]
                        }
                    ]
                }
            ]
        };
    }
}

Below are the changes that are done to the default file

  • Added the import to couple of objects from @pnp/sp, since we are going to populate items from the sample list
  • Assigned the current context to the SP object and then passed to the component.

Let us see the file webpart.tsx

import * as React from 'react';
import { useState, useEffect } from 'react';
import styles from './ListViewDemo.module.scss';
import { IListViewDemoProps } from './IListViewDemoProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
import { useBoolean } from '@uifabric/react-hooks';
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";

const ListViewDemo: React.FC<IListViewDemoProps> = (props) => {
    const {
        description,
        isDarkTheme,
        environmentMessage,
        hasTeamsContext,
        userDisplayName
    } = props;
    const [loading, { setTrue: displayLoading, setFalse: hideLoading }] = useBoolean(true);
    const [listItems, setListItems] = useState<any[]>(undefined);
    const viewFields: IViewField[] = [
        {
            name: 'CaseID',
            displayName: 'Case ID',
            minWidth: 100,
            maxWidth: 150,
            sorting: true
        },
        {
            name: 'CaseStatus',
            displayName: 'Case Status',
            minWidth: 100,
            maxWidth: 150
        },
        {
            name: 'BizSegment',
            displayName: 'Business Segment',
            minWidth: 100,
            maxWidth: 150
        },
        {
            name: 'Author.Title',
            displayName: 'Created By',
            minWidth: 200,
            maxWidth: 250,
            sorting: true
        }
    ];
    const groupFields: IGrouping[] = [
        {
            name: 'Author.Title',
            order: GroupOrder.ascending
        }
    ];

    const _getListItems = async (): Promise<void> => {
        const items: any[] = await props.sp.web.lists.getByTitle('Case Master').items
            .select('CaseID', 'CaseStatus', 'BizSegment', 'Author/Title')
            .expand('Author')
            ();
        console.log("List Items: ", items);
        setListItems(items);
        hideLoading();
    };

    const _getSelectedItem = (items: any[]) => {
        console.log('Selected Item(s): ', items);
    }

    useEffect(() => { _getListItems() }, []);

    return (
        <section className={`${styles.listViewDemo} ${hasTeamsContext ? styles.teams : ''}`}>
            <div className={styles.welcome}>
                <img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
                <h2>Well done, {escape(userDisplayName)}!</h2>
            </div>
            <div>
                {loading ? (
                    <div>Loading, Please wait...</div>
                ) : (
                    <ListView
                        items={listItems}
                        viewFields={viewFields}
                        compact={true}
                        selectionMode={SelectionMode.multiple}
                        selection={_getSelectedItem}
                        showFilter={true}
                        filterPlaceHolder="Search..."
                        stickyHeader={true}
                        groupByFields={groupFields}
                    />
                )}
            </div>
        </section>
    );
};

export default ListViewDemo;
  • The above code use React hooks instead of class component for better understanding
  • Add the required imports for Listview and PnPJs.
  • Created 2 state variable.
    • To display loading text untill fetching the items from the list
    • Store the items fetched from the list.
  • By default, displaying the loading text and on the component mount, fetching the items from the list and store it in the variable and also hide the loading text
  • The items state variable is assigned to the Listview control to display the items in the list view.
  • Also created some of the constants
    • viewFields – To load the fields in the list view. (This is mandatory to display the items on the list view)
    • groupFields – To group the view based on the field. (This is non-mandatory)
  • The items are loaded when the component mount using the useeffect method.

Following are some of the key properties provided by the ListView control

  • items – Items to render in the control
  • viewFields – List of fields to be rendered in the control
  • compact – Use this property to render the contents in the compact mode
  • selectionMode – To enable selection of the item(s). The selection mode can be single or multiple.
  • selection – If you want to perform any operation using the selected item(s)
  • groupByFields – Required if you need to group the display of items based on the field
  • showFilter – To enable search for the users to search the list of items
  • stickyHeader – This will freeze the header which is very useful when trying to display lot of items

Reference URL

ListView – @pnp/spfx-controls-react

Sample Implementation

Conclussion

I hope you had learned something about one of the pnp control. There are lot to come in future.

Please free to post your comments and let me know if you want me to post article on any particular feature or plugins that relates to SharePoint. Thanks for your support.

The next post will be how to extend this ListView for sorting, adding context menu and how to customize the field rendering.

sudharsank/pnpcontrols-demo: Demo project on different PnP React Controls. (github.com)

Happy Coding…

One thought on “PnP React Controls Part 4 – Listview Control

  1. Pingback: PnP React Controls Part 4.1 – Extending ListView Control | Knowledge Share

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s