The entire development pattern of SharePoint Modern Sites is revolving around the SharePoint Framework (SPFx). As part of that, let us have a look on how to create a SharePoint Extension to Add a Custom Context Menu in a SharePoint Modern List using SPFx Extensions.
Its been quite sometime the Extension has released. But, again, most of the times, I have used the Extension for the Application customizer to add Header, Footer, JS files etc., Refer to the link here for the Application Customizer example. The Command Customizer is the one which I tried for one of our requirements and wanted to share with the community.
As usual, let us go with the Step by Step process to create the solution and the explanation.
1. I assume that we are familiar with the SPFx development and setup the dev environment. If not, recommend reading the earlier article here.
2. So, let us jump directly to the CMDer. Open the Commander.
3. I have created a folder called CustomContextMenu and in the Command Prompt, I am on that folder.
4. Let us create a solution by the Yeoman Generator. Yo @microsoft/sharepoint
5. Then enter the Values appropriately as shown below.
6. Select the Extension in the option.
7. Select the List View Command Set
8. Enter the Command Set name and description.
9. Then the Yeoman will generate the required files for us. This will take a couple of mins.
10. Once, the solution got created, we will see the below message.
11. Once the solution got created, let us do a “gulp build” to ensure that the solution got created properly.
12. Open the Source code by typing “code .”
13. This will open the source code in Visual Studio Code.
14. The solution will looks like below.
15. Open the Package-solution.json file under the Config folder and ensure that a new Feature is added.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "custom-context-menu-client-side-solution",
"id": "8656b732-5918-40fb-a808-29815a54cff4",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"features": [
{
"title": "Application Extension - Deployment of custom action.",
"description": "Deploys a custom action with ClientSideComponentId association",
"id": "95012047-e17f-4bbe-ac39-0fec881ef1aa",
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml",
"clientsideinstance.xml"
]
}
}
]
},
"paths": {
"zippedPackage": "solution/custom-context-menu.sppkg"
}
}
16. On the write-manifest.json file, ensure that the CDN Path is given properly. By default there will be a place holder. But, either we can give it to the publicCDN or the SharePoint Library itself. During the Development Time, I suggest to keep it on the SharePOint Library and when moving to production, we can change this to publicCDN and deliver accordingly. Moreover, there is a flag includeClientSideAssets in the package-solution.json file which has an impact on the assets to be copied along with the package. Let us explain this in detail in the upcoming articles. As of now, let us focus on the cdn path as a sharepoint online document library.
17. Give the value as shown below. I have created a Doclib and a folder in it.
18. The value in the write-manifest.json will be as below.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "/sites/ReactRepository/ClientSideAssets/CustomContextMenuAssets"
}
19. And one important thing is, remove the clientsideinstance.xml file from the package-solution.json file, as we don’t require this file to be a part of our solution. Let us focus on this as well in a separate article. Basically, this file requires the tenant admin to deploy the solution to all the site collections, without any feature activation. Let us focus this in detail in the upcoming articles.
20. Hence, the final package file will be
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "custom-context-menu-client-side-solution",
"id": "8656b732-5918-40fb-a808-29815a54cff4",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"features": [
{
"title": "Application Extension - Deployment of custom action.",
"description": "Deploys a custom action with ClientSideComponentId association",
"id": "95012047-e17f-4bbe-ac39-0fec881ef1aa",
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml"
]
}
}
]
},
"paths": {
"zippedPackage": "solution/custom-context-menu.sppkg"
}
}
21. Now, let us focus on the elements.xml. This is were we actually have our custom action code.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Title="ExportToDoc"
RegistrationId="100"
RegistrationType="List"
Location="ClientSideExtension.ListViewCommandSet.ContextMenu"
ClientSideComponentId="c8a8aadd-5bcc-4a67-9513-017094070d4d"
ClientSideComponentProperties="{"sampleTextOne":"One item is selected in the list.", "sampleTextTwo":"This command is always visible."}">
</CustomAction>
</Elements>
Ø Title : Give any Title, that will be Unique.
Ø RegistrationId : Template ID of the List (100 for the Custom List and 101 for the Library)
Ø RegistrationType = “List”
Ø Location: There are three values which we can give.
· ClientSideExtension.ListViewCommandSet.CommandBar – This will be available on the Command Bar
· ClientSideExtension.ListViewCommandSet.ContextMenu – This will appear as a context menu. In my case, I am using this, based on my requirement.
· ClientSideExtension.ListViewCommandSet – This, will give both the options. CommandBar and ContextMenu.
Ø ClientSideComponentId : This we need to copy from the extension manifest json. Let me show when we come to that file.
Ø ClientSideComponentProperties : If our command/menu requires any properties, then we can give those properties values here. Otherwise, we can remove this line. In my case, I am removing this, as I don’t want to pass any properties to the menu.
22. The Extension Manifest File will be as shown below.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/command-set-extension-manifest.schema.json",
"id": "c8a8aadd-5bcc-4a67-9513-017094070d4d",
"alias": "ExportToDocCommandSet",
"componentType": "Extension",
"extensionType": "ListViewCommandSet",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"items": {
"ExportToDoc": {
"title": { "default": "Export to Doc" },
"type": "command"
}
}
}
Ø Make a note of the ID and the same ID should be present on the elements.xml in the ClientSideComponentID.
Ø The items also, the “ExportToDoc” is the value which we have given on the elements.xml.
Ø Give the Title as a User readable, as this is what going to appear on the Context Menu. In our case, we have given it as “Export to Doc”.
23. Now, the last file, which we need to focus is the actual CommandSet.ts file.
import { override } from '@microsoft/decorators';
import { Log, Guid } from '@microsoft/sp-core-library';
import {
BaseListViewCommandSet,
Command,
IListViewCommandSetListViewUpdatedParameters,
IListViewCommandSetExecuteEventParameters
} from '@microsoft/sp-listview-extensibility';
import { Dialog } from '@microsoft/sp-dialog';
import * as strings from 'ExportToDocCommandSetStrings';
export interface IExportToDocCommandSetProperties {
}
const LOG_SOURCE: string = 'ExportToDocCommandSet';
export default class ExportToDocCommandSet extends BaseListViewCommandSet<IExportToDocCommandSetProperties> {
@override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized ExportToDocCommandSet');
return Promise.resolve();
}
@override
public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
const compareOneCommand: Command = this.tryGetCommand('ExportToDoc');
if (compareOneCommand) {
// This command should be hidden unless exactly one row is selected.
compareOneCommand.visible = event.selectedRows.length === 1;
}
}
@override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'ExportToDoc':
const itemId: number = event.selectedRows[0].getValueByName("ID");
const listId: Guid = this.context.pageContext.list.id;
const listName: string = this.context.pageContext.list.title;
alert('Entered the Click Event');
Dialog.alert(`${itemId} - ${listName} - ${listId}`);
break;
default:
throw new Error('Unknown command');
}
}
}
Basically, there are three methods which we are overriding.
Ø OnInit : We are just logging that, the command set is triggered.
Ø onListViewUpdated : This event, will get triggered once the list view is rendered. Here, we can decide whether our Menu needs to be appear only for one item or more than one item.
Ø onExecute : This is the place, where exactly, we are writing our logic. The current list and the Item IDs can be taken as below.
const itemId: number = event.selectedRows[0].getValueByName(“ID”);
const listId: Guid = this.context.pageContext.list.id;
const listName: string = this.context.pageContext.list.title;
alert(‘Entered the Click Event’);
Dialog.alert(`${itemId} – ${listName} – ${listId}`);
With this, we can do whatever, we want for that Item.
24. Now, coming back to the deployment.
25. Run the “Gulp Build” to build the solution
26. Run the “Gulp Bundle –ship” to create the bundle
27. Run the “Gulp Package-solution –ship” to create the package (SPPKG) file.
28. Go to the AppCatalog Site collection and upload the SPPKG file.
29. Go to the Site Collection and go to the CDN Library which we created. In our case it is, ClientSideAssets/CustomContextMenuAssets and upload the files from the temp/deploy folder to this path.
30. Come back to the Lists and click on the Three dots. We will be able to see the new Context Menu as below.
This article details about how to create a context menu in the Modern SharePoint Lists and Libraries and the corresponding actions, which we can do on the click of the Menu. Hope this helps.
Happy Coding,
Sathish Nadarajan.
Leave a comment