Introduction
When developing custom client web parts using the SharePoint Framework (SPFx) you invariably need a add web part property editors to allow users to set appropriate values so that the web part can be appropriately configured. SharePoint provides a stack of standard web part property controls that you can use for this purpose and I have blogged about how to use them in a previous post.
Most of these controls provide developers with a “disabled” attribute which can be set to a Boolean, true or false value based on other property values and business logic defined in the web part. If a user sets a choice option to some value in an option control then business logic might dictate that other controls are not relevant for the chosen setting and so those controls should be disabled.
As an example, consider the following fragment of the getPropertyPaneConfiguration method in a sample web part. This method is where you, as a developer, will define the controls to be displayed to users when they configure your creations.
What I have done here is created 2 simple property pane controls and hooked them up to web part properties. The first is a checkbox control used to set the true/false value of the ‘hideWhenNoControls’ property of the web part, which is a simple Boolean property. The second is a textbox, which is hooked up to the ‘noItemsText’ web part property. Now look at the disabled property of the text box control and you will see that the setting, disabled or not, is driven by the property set by the checkbox control.
Why do we need to conditionally control the behaviour of property controls?
Although conditionally disabling controls is not absolutely essential it does serve to make the web part more usable and help users understand which properties are relevant in which contexts.
In my example web part above, the idea is to only allow users to provide a value in the text box when they set the checkbox to not render anything if the web part has nothing to display (say the purpose of the web part is to return items from a list but the list is empty). When the checkbox is not checked (and the web part has nothing to report) then the custom message in the text box will be displayed, otherwise the web part will render nothing at all and will, for all intents and purposes, be invisible on the page.
This all works well enough. Here is the property pane when the checkbox is checked and unchecked respectively.
Conditionally Displaying Property Controls
Disabling the controls is fine, as it goes but look at that wasted space we have when the checkbox is unchecked. Wouldn’t be great to have flag like ‘disabled’, let’s call it ‘hidden’, which would conditionally display controls rather than rending them albeit in a maybe disabled state? Unfortunately, no such flag exists .
The often-used work around for this is to use your business logic condition to either return the desired control to be rendered or a substitute PropertyPaneLabel control as explained in this post.
In demonstrate this technique we can adapt the code in the getPropertyPaneConfiguration method to be something like the following:
Note how the selection of component (textbox or empty label) is assigned to a variable which returns the right control based the same business logic as before. We don’t need to conditionally disable the textbox any more. because we are now only showing it when we need it.
So, this is how it now looks in the UI.
Awesome, just what we want. Check the checkbox and the textbox appears, uncheck it and the textbox is hidden.
Not so fast! What’s that space doing between my checkbox control and other controls which come after it? Well it turns out that that PropertyPaneLabel control is still being rendered, even though we set the text to any empty string, and unfortunately still taking up some space.
Sadly, we can’t simple return null or an empty string from out condition either, as SPFx spits the dummy at that and refuses to render your property pane at all!
One way to work around this unwanted space issue is to shift the problem control to be the last one in the group and that way, although we will still render the label control and taking up space, hopefully no one will notice because the space will come at the end of the group of controls and not in the middle somewhere. So, by re-ordering controls can sort things out.
Ok, this works well enough but only gets us so far. What if I don’t want to be forced to order my controls so that conditionally displayed controls are shown last and what if I have multiple controls I want to show conditionally, they’ll just stack on top of each other and then that additional space at the end of the group that nobody noticed, suddenly becomes noticeable? We need a better approach.
The Empty Property Pane Control
What I reckon we need to do is not abuse the PropertyPaneLabel control but instead create our own custom property pane control which, well, does nothing. This is a strange case where we have a need for a control that doesn’t do anything – how peculiar. I don’t think I’ve ever done that before!
In the end, it turned out not to be too hard to do, but I did need to do some research and was assisted magnificently by this article written by that SharePoint Maestro, Waldek Mastykarz.
So, what I did was to add a new TypeScript file to the project in Visual Studio Code and create a new EmptyControl class that implements the IPropertyPaneField interface as shown below:
There are a few subtleties, which is where Waldek put me on track. First, because the EmptyControl class implements the IPropertyPaneField class we need are obliged to conform to the interface contract and need to implement properties, type, targetProperty properties even if we aren’t using them.
Note that the properties property must also adhere to the IPropertyPaneCustomFieldProps interface. This interface demands we supply 2 mandatory properties namely:
- onRender: Which we bind to a class method that renders any output.
- key: Which is a string value.
We assign values to the properties property in the class constructor. Note that key property must be a assigned a value (even if we have no need for one), the framework will spit the dummy if you assign it an empty string value.
Now, the render method is interesting, because before I stated that this control does nothing but here we are, rendering a div element albeit an empty one. What’s going on?
Well, it turns out that higher up the chain of command, property controls get inserted in a container div which is assigned the CSS class propertyPaneGroupField. This has the irksome effect of adding a padding-top attribute of 4px.
As this attribute is set on the container which hosts the control and not part of the control itself, we can’t change this directly from within the control. Here’s what the control would look like if we did nothing in the render method.
This is unquestionably an improvement on the space sucked up by the PropertyPaneLabel control but if you look closely you can see that the desired effect is spoilt by those pesky 4 pixels – grrr.
Whilst we can’t directly change properties of the parent div, we can counter the effect by adding an empty div of our own and then nudging up the margin by those 4 pixels. That then is what we are doing in render method. We are simply inserting an inner div with the top-margin set to offset the 4-pixel padding. It’s a bit inelegant, I grant you, but it does get the job done.
Using the Empty Control
To use our control, you’ll have to import the EmptyControl class into you web part class of course. I saved mine in a file called PropertyControls.ts.
import {EmptyControl} from ‘./PropertyControls’;
Then, to use a control instances you could just do as follows:
However, we might need multiple empty controls and as such we don’t need to create a new instance, we can use the same instance over and over.
As such, my preferred way of using the control is to wrap a control instance returned by an emptyControl defined as a property of the web part as shown below:
This technique means we only create an empty control instance when we need to and we can ruses the same empty control instance whenever one is needed.
We just need to update the reference in the getPropertyPaneConfiguration method to this:
When to use and Empty Control
This technique really comes into its own when we have complex web parts with various controls that should be hidden or shown, depending on the property values set by other controls.
The screenshot below is for the properties of a Chart Web Part I am working on.
In this example, the conditional display of controls is based on the type of the column (the chart data is being driven by SharePoint list content) which is used to group data. The selected group-by column is set in the column option picker control and I needed to display different formatting options depending on the type of column which was selected by the user.
I started out by conditionally disabling these controls but it made the UI very messy. By using the Empty Control technique described in this article I was able to smarten things up considerable.
The screenshot above shows when a simple (single value) choice column is selected. If the selected grouping column supports multiple values (multi-user, multi-choice, multi-taxonomy or multi-lookup) I wanted to ask the user if the set of column values should be combined into a single group or split out into separated values, and so I needed to display a checkbox for them to provide this guidance.
When the column is a Date/Time column I needed to ask the user to select a date format.
And, just to nest things deeper, if the user selects the Custom date format option, I want to present them with a textbox control in which they can specify a custom date format string.
As you can see, everything is correctly spaced out as it should be.
Summary
And there we have it. Conditionally displaying property editors for SPFx web parts is not something that is natively supported, the only native support you have is to disable these controls. Sometimes, disabling controls is just fine and indeed the most appropriate thing to do. However, there are times when it is better to be able to hide a property editor control altogether, rather than disabling it.
You can use the quick and dirty technique to optionally render a control as an empty label but that adds unwanted space to the UI. If you only need to hide one control and you can easily move it to being the last control in the group then this approach will generally work out fine. There will still be space, but no one will really notice it unless they look closely.
However, when your needs are more complex, we need a different approach and the one that I have outline in this post steps up to mark. Basically, we create a new custom property control which does nothing other than close a 4px gap caused by padding applied to the parent containing div of the control.
Please let me know if you have any ideas or suggestions for improvements.