This post is about creating a ListView CommandSet extension which uses the the onInit method to make “service” calls.

If you want to dive straight in, the source code for this post is available from my-command-set GitHub repository.

Full details can be found on creating an Extension solution can be found on the SharePoint Framework Extension Documentation Site, but I’ve given a whistle stop tour below.

Creating an Extension project

This is done using the Yeoman SharePoint Generator in a terminal of your choice.

1
2
3
md my-command-set
cd app-extension
yo @microsoft/sharepoint

Enter the following options

1
2
3
4
5
6
7
8
9
10
? What is your solution name? my-command-set
? Which baseline packages... ? SharePoint Online only (latest)
? Where do you ... files? Use the current folder
? Do you want ... tenant admin ... in sites? No
? Will the components ... APIs ... tenant? No
? Which type of client-side component to create? Extension
? Which type of client-side extension to create? ListView Command Set
Add new Command Set to solution my-command-set.
? What is your Command Set name? SecurableCommandSet
? What is your Command Set description? SecurableCommandSet description

Wait for Yeoman to do it’s thing…

And then wait a little longer…

I use Visual Studio Code so if you’re joining me on this journey…

1
code .

Hopefully you’ll see these files in your repo

First of all lets setup the CommandSet in the manifest.

  • Navigate to src\extensions\securableCommandSet folder
  • Open the SecurableCommandSetCommandSet.manifest.json file

Updating the project files

Remove the two commands that were created with the project

1
2
3
4
5
6
7
8
9
10
11
12
"items": {
"COMMAND_1": {
"title": { "default": "Command One" },
"iconImageUrl": "icons/request.png",
"type": "command"
},
"COMMAND_2": {
"title": { "default": "Command Two" },
"iconImageUrl": "icons/cancel.png",
"type": "command"
}
}

Replace them with the command that’s going to be secured

1
2
3
4
5
6
7
"items": {
"CMD_SECURE": {
"title": { "default": "Secret Command" },
"iconImageUrl": "data:image...",
"type": "command"
}
}

Full base64-encoded image is included with source code

Next open up SecurableCommandSetCommandSet.ts

Add a private field to the classe to store the visibility of the command.

1
private isInOwnersGroup: boolean = false;

We want to make sure the command is only visible to people who are in the Owners group of the site we are in.

This is done in the onListViewUpdated method of the SecurableCommandSetCommandSet class.

Below is the code added when the project is created.

1
2
3
4
5
6
7
8
@override
public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
if (compareOneCommand) {
// This command should be hidden unless exactly one row is selected.
compareOneCommand.visible = event.selectedRows.length === 1;
}
}

Replace it with following

1
2
3
4
5
6
7
8
9
@override
public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
const compareSecureCommand: Command = this.tryGetCommand('CMD_SECURE');
if (compareSecureCommand) {

compareSecureCommand.visible = this.isInOwnersGroup;
}

}

Install the PnP client side libraries as we’re going to need some of their magic in this solution

1
npm i @pnp/sp @pnp/common @pnp/logging @pnp/odata --save

SharePoint Groups and their members aren’t available in the BaseListViewCommandSet.context property, so we’re going to need to load them.

The problem is that this will have to be done using Promises and onListViewUpdated doesn’t return a promise.

Luckily we have the onInit method for this (returns Promise<void>). This method gets called when you component is initialised (Basically when the list view is loaded up in the page). Anything in the onInit method will run before the commands are rendered, similar to the actions you’d run in the componentWillMount method of a react component.

To use the pnpjs library it needs to be initialised first and this needs to be done in the onInit method.

Add the import statement

1
import { sp } from "@pnp/sp";

Replace the onInit method with the following code, this sets up the sp helper with context of the Extension and then to call into the SharePoint Groups in the site, we’re going to have to await away in the onInit method again to call into the site and set the isInOwnersGroup field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@override
public async onInit(): Promise<void> {

await super.onInit();

await sp.setup({ spfxContext: this.context });

const email: string = this.context.pageContext.user.email;
const ownerGroup: SiteGroup = sp.web.associatedOwnerGroup;
const users: SPUser[] = await ownerGroup.users.get();

this.isInOwnersGroup = users.some((user: any) => user.Email === email);

return Promise.resolve<void>();
}

For the observant people out there, you may notice that I’ve declared user as any. For some reason, the users collection returned has UpperCamelCase properties and the TypeScript reference is using lowerCamelCase, which was causing a TypeScript compile error. Hence the user.Email === email rather than user.email === email in the some function call.

In this snippet of code, we’re getting the login of the current user, the associated owner group of the site and then getting the users in the group.

The some function determines if the user is in the group and it’s result sets the isInOwnersGroup.

Finally an update is needed on the onListViewUpdated method to show / hide the command.

1
2
3
4
5
6
const compareSecureCommand: Command = this.tryGetCommand('CMD_SECURE');

if (compareSecureCommand) {

compareSecureCommand.visible = this.isInOwnersGroup;
}

Add the new command to the onExecute method to make sure it gets picked up in the switch statement

1
2
3
4
5
6
7
switch (event.itemId) {
case 'CMD_SECURE':
Dialog.alert("Shhhhhh! It's a secret...");
break;
default:
throw new Error('Unknown command');
}

Now we’re ready to gulp serve

Open up serve.json in the config directory

Change the two pageUrl properties to a list in your tenant

1
"pageUrl": "https://contoso.sharepoint.com/sites/mySite/SitePages/myPage.aspx"

Currently there is a bug in tenants that are on First Release that stops gulp serve working correctly. If you can’t see your command then switch to Standard (not always instant!), if it still doesn’t work then try deploying without the --ship paramter. See all the details on the sp-dev-docs GitHub repository

Make sure you account is in the Owners group (It won’t be if you created a “groupified” team site).

If all is good your new CommandSet should appear on the top menu and will show the alert message when clicked.

Summary

Getting the SharePoint group users is just an example of how you can use the onInit method to call into other services, like custom web apis, MS Graph, etc.

Remember that this could effect the load time of your command, which may effect the user experience. The context menu may not be on screen for long, so your menu may have not loaded before it’s gone.