Monday, January 8, 2018

Explaining PropertyPaneAsyncDropdown

PropertyPaneAsyncPaneDropdown is there 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 . 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 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. keyand 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 );

}

stateKey & onChanged 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 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, it is also self explanatory.

Here is a link to src folder for this webpart. Src
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.

No comments:

Post a Comment