This is the second post of three that explain how to add dependent properties to Client Web Part Property Pane. As an example of such properties I decided to use Lists dropdown and Views dropdown. Views dropdown is populated based on selected List. Here is a content of 3 posts: As a result of these three posts we'll have a fully working web part with ability to select List View in Property Pane:
This post describes how to create custom Property Pane Field with 2 simple unstyled dropdowns (<select> elements).
The main idea of creating this web part is to be able to populate Views dropdown dynamically when user selects value in Lists dropdown.
First idea was to use 2 PropertyPaneDropdown controls. If user selects something from one dropdown we're doing something to get data and push this data to the second dropdown.
It won't work for at least 3 reasons:
Data request is an asynchronous operation. But I didn't find any method to override that will wait for this data before rerender the property pane.
Property Pane rerender and propertyPaneSettings property getter are called dynamically only if web part property pane behavior is set to Reactive. It means that you won't be able to do anything in non-Reactive property pane (when there is an Apply button at the bottom of the pane).
You can't initiate Property Pane refresh from your custom field or from web part.
That's why the only way to implement dependent properties is to create custom Property Pane Field that will contain both dropdowns in it.
Let's consider that our custom Property Pane Field is named PropertyPaneViewSelector. It will have 2 properties that should be available in web part (and saved with web part): List Id and View Id.
To make these properties available in web part we need to update Web Part Properties interface. In our case it is named as IDepPropsWebPartProps. We can either add 2 separate properties there or create a complex object with 2 properties in it. I think that the second option is logically better because List Id and View Id will be populated from a single Property Pane Field.
So let's create an interface for the complex property object first (I put it in /webparts/depProps/controls/Common.ts) and then change the IDepPropsWebPartProps
IPropertyPaneViewSelectorProps
And in IDepPropsWebPartProps.ts
Now we can access these props in web part class like this.properties.depProps.listId and this.properties.depProps.viewId. Let's set defaults for these properties. For that we need to go to DepPropsWebPart.manifest.jsonand in preconfiguredEntries object change properties property like that:
Let's change web part render method to display currently selected List Id and View Id:
Now let's start with custom Property Pane Field. To create such field we need to create a class that implements IPropertyPaneField interface:.
As you can see it's a so called Generic interface and when implemented TProperties should be changed to some specific interface. And one more thing about this interface - it should extend IPropertyPaneCustomFieldProps if we want to be able to customize rendering. And this interface will be used to pass parameters to the constructor of our field. It means it should contain all the properties we need to get from web part to initialize the field correctly. So we need to think what properties we need in our field.
onRender and onDispose? from base interface
listId and viewId - our main data
also we need some labels to display near dropdowns. Of course we can hardcode them or get from some source inside the field. But we can set them from web part. It will make our field more isolated and flexible. Let's name these properties as listLabel and viewLabel
we will use data helpers created in previous post. And these helpers use web part context. So we need this context in our properties as well.
we need to notify web part that something has changed in our field. For this purpose we'll add onPropertyChange property in the interface to get the handler from a web part.
Final interface will look like that (/webparts/depProps/controls/Common.ts):
You can ask why it is named with Internal prefix. I'll describe it in a second. For now our custom class will look like that (/webparts/depProps/controls/PropertyPaneViewSelector.ts):
If you look at out-of-the-box Property Pane Fields, you'll see that field classes are not created directly. There is always a helper function that creates an instance of Field's class. Let's create helper function for our class. Also this function will allow us to hide onRender and onDispose as we want to implement them internally. That's why we created Internal properties interface. Let's create 'external' one (/webparts/depProps/controls/Common.ts):
and a helper function (/webparts/depProps/controls/PropertyPaneViewSelector.ts):
Now we can call the helper function from web part propertyPaneSettings getter:
Now we need to implement UI for the Field and logic to get data from our data helpers. In this post I'll show example of simple select elements without Office UI Fabric-like styling. I prefer to use Knockout.js as a framework (because I know it better than React and because looks like it is used for Lists and Libraries new experience). To use Knockout we need to install npm module:
And add path to knockout js file in config/config.json in externals section:
So we'll create Model, View and ViewModel for our field. Let's start with model. It should retrieve lists and views from data helpers and... that's it(/webparts/depProps/controls/PropertyPaneViewSelectorModel.ts):
The view will contain 2 select elements with bindings (in the GitHub repository the view code is updated to render Office UI Fabric-like markup that will be described in the next post):
And finally ViewModel that contains binded properties and logic to update UI and initiate web part update (in the Git repository the ViewModel code is updated to work with Office UI Fabric-like dropdowns that will be described in the next post):
Final step is to call View's rendering in Field render method, initialize ViewModel and apply Knockout bindings:
Now you can see the result: There is a lot of code here in the post. But there is nothing really difficult to understand. Let me know if you have any questions or comments. See you in next post. Have fun!
Comments