PnP React Controls Part 4.1 – Extending ListView Control

Introduction

Hi friends, this is part 4.1 which is the extended post of PnP React Controls Part 4 – Listview Control. We will see how we can customize the default rendering of the fields and also see how we can add contextual menu to the list view control.

Note: Since this is the continuation of the earlier post, I will use the same web part but with the different control to render when the web part loads.

Focus on the Code

Let us the see the new tsx file created for Extended List View demo which is ExtendListViewDemo.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";
import { Persona, PersonaSize } from 'office-ui-fabric-react/lib/Persona';
import { IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import * as moment from 'moment';
import ListViewContextMenu from './ListViewContextMenu';

const ExtendListViewDemo: 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: '',
            displayName: '',
            sorting: false,
            maxWidth: 40,
            render: (rowItem: any) => {
                return (
                    <ListViewContextMenu item={rowItem} />
                );
            }
        },
        {
            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,
            render: (item?: any, index?: number, column?: IColumn) => {
                return (
                    <Persona size={PersonaSize.size24} showInitialsUntilImageLoads imageShouldStartVisible
                        text={item['Author.Title']} imageUrl={`/_layouts/15/userphoto.aspx?username=${item['Author.EMail']}&size=M`} />
                );
            }
        },
        {
            name: 'Created',
            displayName: 'Created',
            minWidth: 200,
            maxWidth: 250,
            sorting: true,
            render: (item?: any, index?: number, column?: IColumn) => {
                return (
                    <div>{moment(item.Created).format("DD-MMM-YYYY")}</div>
                );
            }
        }
    ];
    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', 'Author/EMail', 'Created')
            .expand('Author')
            ();
        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 ExtendListViewDemo;

There are no changes done on the items rendering part, the items are rendered from the same list which is used in the previous post. Below are the changes done

  • Custom render is added on the Author.Title field to render the author name as a Persona component which will show the image with author name.
  • Added the Created field to the _getListItems method
  • Custom render is added to the Created field to render the created date in the format DD-MMM-YYYY using momentjs.
  • Added a new field after the CaseID field to render the ContextMenu. A custom control named ListViewContextMenu is rendered and we will see the control in detail.

Let us see the ListViewContextMenu.tsx control

import * as React from 'react';
import { Layer, IconButton, IButtonProps } from 'office-ui-fabric-react';
import { ContextualMenuItemType, IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu';
import styles from './ListViewDemo.module.scss';

export interface IContextMenuProps {
    item: any;
}

const ListViewContextMenu: React.FC<IContextMenuProps> = (props) => {

    const _handleClick = (item: IContextualMenuItem, msg: string): void => {
        console.log(`Key: ${item.key} - Argument: ${msg}`);
    };

    return (
        <div className={styles.divContextMenu}>
            <IconButton id='ContextualMenuButton1'
                text=''
                width='25'
                split={false}
                iconProps={{ iconName: 'MoreVertical' }}
                menuIconProps={{ iconName: '' }}
                menuProps={{
                    shouldFocusOnMount: true,
                    items: [
                        {
                            key: 'View',
                            name: 'View Case',
                            onClick: (e, i) => _handleClick(i, props.item.CaseID + ' Case ID')
                        },
                        {
                            key: 'divider_1',
                            itemType: ContextualMenuItemType.Divider
                        },
                        {
                            key: 'edit',
                            name: 'Edit Case',
                            onClick: (e, i) => _handleClick(i, props.item.CaseStatus + ' Case Status')
                        },
                        {
                            key: 'share',
                            name: 'Share Case',
                            onClick: (e, i) => _handleClick(i, props.item.BizSegment + ' Business Segment')
                        },
                        {
                            key: 'delete',
                            name: 'Delete Case',
                            disabled: true,
                            onClick: () => console.error('Disabled action should not be clickable.')
                        }
                    ]
                }} />
        </div>
    );
}

export default ListViewContextMenu;
  • Above is the control which will be rendered as a context menu for the list view.
  • Created a props file with just item where we will pass the individual row item to this control
  • We are using IconButton with some menu items
    • View Case
    • Edit Case
    • Share Case
    • Delete Case – Disabled
  • All the menu items are linked to the _handleClick method where below are the auguments passed
    • i – IContextualMenuItem which has the key and name and based on the key we can perform different operations like view, edit or delete etc.
    • other Info – Any other information you would like to pass to the click method, in this demo I had passed some string with the item properties.
  • We can also enable or disable, show or hide menu items based on certain conditions checking on the logged in user permissions, their roles etc.

Reference URL

ListView – @pnp/spfx-controls-react

Sample Implementation

Conclusion

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 use custom paging in ListView.

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

Happy Coding…

One thought on “PnP React Controls Part 4.1 – Extending ListView Control

  1. Pingback: PnP React Controls Part 4.2 – Extending ListView Control with Custom Paging | 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