65.9K
CodeProject is changing. Read more.
Home

Explaining PropertyPaneAsyncDropdown

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1 vote)

Jan 9, 2018

CPOL

3 min read

viewsIcon

6224

downloadIcon

17

About PropertyPaneAsyncDropdown

Preface

I recently started working on SpFX framework for SharePoint client side development. I was going through SpFx Pnp samples. While samples are great, online documentation is not. It is verbose yet cryptic. I thought I'd break the samples down for my own learning & write these blogs to simplify for anyone else who picks up these samples to get his/her feet wet with SpFX. There might be a series of posts in coming days if don't lose interest or don't find anything better to do 😃

SpFx samples are available here: https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples .

We'd look into react related samples.

PropertyPaneAsyncPaneDropdown is there in PnP samples as an example of asynchronously populated "select" control in property pane. There are 4 typeScript interfaces in play and we should discuss them first.

IPropertyPaneAsyncDropdownProps

First one up is IPropertyPaneAsyncDropdownProps. The purpose of this interface is to pass information from webpart file to the custom control, i.e., PropertyPaneAsyncDropdown. Interface looks like:

export interface IPropertyPaneAsyncDropdownProps 
{ 
label : string ;
loadOptions : () => Promise < IDropdownOption []>;
onPropertyChange : ( propertyPath : string , newValue : any ) => void ;
selectedKey : string | number ;
disabled ?: boolean ;
}
  • label: Label of the property in property pane.
  • loadOptions: Method to fetch options for the dropdown, in our case method to fetch list information from Sharepoint so that user can select a list name in property pane.
  • onPropertyChange: Method to execute when user selects an item in the dropdown, i.e., when user selects a list.
  • selectedKey: Value from the selected option in dropdown. In our case, it is the list Url of selected list.
  • disabled: We are not using it but could be used to render the control disabled.

IPropertyPaneAsyncDropdownInternalProps

Second up is IPropertyPaneAsyncDropdownInternalProps. This interface extends IPropertyPaneAsyncDropdownProps (our own) and IPropertyPaneCustomFieldProps (imported from @microsoft/sp-webpart-base) and it doesn't implement any attribute of its own. We need an instance of IPropertyPaneCustomFieldProps to work with a custom property control & IPropertyPaneAsyncDropdownInternalProps is an instance of one. Besides the attributes we defined in our IPropertyPaneAsyncDropdownProps, we'd be using onRender attribute (inherited from IPropertyPaneCustomFieldProps). 
key is another mandatory attribute from IPropertyPaneCustomFieldProps & we'll set it.
Before we discuss the remaining two interfaces, let's dive into the custom property itself.

PropertyPaneAsyncDropdown

This class implements IPropertyPaneField<IPropertyPaneAsyncDropdownProps>, which dictates that our class should have these three attributes (which we do):

public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
public targetProperty: string;
public properties: IPropertyPaneAsyncDropdownInternalProps;

Well, as per the dictation of implemented interface, properties attribute should have type IPropertyPaneAsyncDropdownProps, but remember IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps so we are OK here.

Now, look at the constructor:

constructor ( targetProperty :  string ,  properties :  IPropertyPaneAsyncDropdownProps ) 
{
this . targetProperty  =  targetProperty ; 
this . properties  =  
{ 
key :  properties . label , 
label :  properties . label , 
loadOptions :  properties . loadOptions , 
onPropertyChange :  properties . onPropertyChange , 
selectedKey :  properties . selectedKey , 
disabled :  properties . disabled , 
onRender :  this . onRender . bind ( this ) 
}; 
}

Remember, we create the instance of this class from webpart itself so we pass the property name from there, also we pass an instance of IPropertyPaneAsyncDropdownProps from webpart code. key and onRender are two attributes which IPropertyPaneAsyncDropdownInternalProps inherits from IPropertyPaneCustomFieldProps and we set key to label and onRender to a locally defined function.

IAsyncDropdownProps

We will discuss this interface in context to where it is used in PropertyPaneAsyncDropdown class in onRender method.

private   onRender ( elem :  HTMLElement ):  void   
{ 
if  (! this . elem ) 
{ 
this . elem  =  elem ; 
} 
 
const   element :  React . ReactElement < IAsyncDropdownProps > = 
React . createElement ( AsyncDropdown , 
{ 
label:   this . properties . label , 
loadOptions:   this . properties . loadOptions , 
onChanged:   this . onChanged . bind ( this ), 
selectedKey:   this . properties . selectedKey , 
disabled:   this . properties . disabled , 
// required to allow the component to be re-rendered by   
//calling this.render() externally   
stateKey:   new   Date (). toString () 
}); 
ReactDom . render ( element ,  elem ); 
} 

stateKeyonChanged are worth discussing. We set stateKey to current time so that every time user opens the property pane, stateKey has a new value. We'd use this fact to reload the select options every time user opens the property pane.

onChanged is set to a locally defined function as:

private onChanged(option: IDropdownOption, index?: number): void {
this.properties.onPropertyChange(this.targetProperty, option.key);
}

All this function does is it calls the method passed by the web part.

ListViewWebPart

This is the webpart file itself. We should look the property setup here.

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration
{
return 
{
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
new PropertyPaneAsyncDropdown('listUrl', {
label: strings.ListFieldLabel,
loadOptions: this.loadLists.bind(this),
onPropertyChange: this.onListChange.bind(this),
selectedKey: this.properties.listUrl
})
]
}
]
}
]
};
}

We should look at the onPropertyChange function here:

private onListChange(propertyPath: string, newValue: any): void
{
const oldValue: any = get(this.properties, propertyPath);
if (oldValue !== newValue)
{
//this.properties.fields = null;
}
// store new value in web part properties
update( this.properties, propertyPath, (): any => newValue );
// refresh property Pane
this.context.propertyPane.refresh();
// refresh web part
this.render();
}

Whenever the user selects a new value in the dropdown, we need to update the webpart property explicitly via "update" call (imported from @microsoft/sp-lodash-subset).

There is one more file "AsyncDropdown.tsx" which plays an important role but this file, while it's long, is also self explanatory.

Here is a link to the src folder for this webpart.

My next step would be to introduce a dependent cascaded drop down control (to select list columns). Eventually, I'd render a list view of selected columns.