Modern SharePoint pages have a new web part editing experience. This is powered by modern web parts and the new SharePoint Framework (SPFx), and developers who wish to work with these new pages need to understand the new model. As you start to build modern web parts, you’ll find the need to implement web part properties – so that your end-users can provide any options or settings required. Microsoft provide a set of common “property pane” controls such as textbox, dropdown, slider and so on – and it’s also possible to add an entirely custom control (something I’ll cover in a future article). In most cases the core controls are your starting point though, so in this article I’ll start looking at the TypeScript/JSON needed to use these controls.
- In this article:
- Reactive vs. non-reactive property panes
- Structuring web part properties into pages and groups
- Textbox, Dropdown and Checkbox controls
- Other articles:
First things first – reactive vs. non-reactive property panes
Perhaps the first thing to understand when implementing web part properties is that by default, the new page framework supports *immediate* changes to the rendering of your web part when a page editor changes a property. This is known as a “reactive” property pane - no more having to click “Save”, “OK”, “OK” just to see a change. Let’s take the example of a web part property specifying the description of the widget - if a user edits this in a textbox, and this is displayed in the main area of the web part, it will be dynamically updated as the user types. This happens automatically, although can require the developer to implement certain things in more complex cases.
Sometimes you want to disable this though – perhaps it’s not appropriate to have the UI constantly updating, or a lookup to SharePoint is needed each time there’s a change. You can disable the reactive behavior by adding this code to your web part class:
protected get disableReactivePropertyChanges(): boolean {
return true;
}
Structuring web part properties into pages and groups
Perhaps the second thing to mention is that the new web part property pane can be structured into multiple pages, each with groups of properties. And the best news is that we don’t have the baggage of the old web part properties on every single web part that no-one ever used (“Allow Minimize”, “Allow Close” etc.). So in the new world, a simple web part property pane with a couple of pages and groups might look something like the images below – take note of the page title, group title and paging controls:
This allows us to better structure our properties, and so in my example the page 2 of the web part props might look like this:
You can continue to define additional pages and the framework will take care of providing paging in the UI for you. In terms of the code to do this (and define SPFx web part properties in general), all the action happens in the getPropertyPaneConfiguration() method. Here’s the code for the above (by the way, I’m using Gist for longer code samples like this one but have others “inline” in the post, so apologies that they look slightly different!) – anyway, we basically have two pages being defined in our structure now:
Using the different controls
So now we understand how to split our controls into pages and groups, let’s move on to the controls themselves. The first step in editing your web part code is to add import statements for TypeScript modules – we do this for each control we wish to use. So at the top of your web part class, move from something like this:
..to..
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
IWebPartContext,
PropertyPaneTextField,
PropertyPaneCheckbox,
PropertyPaneChoiceGroup,
PropertyPaneDropdown,
PropertyPaneSlider,
PropertyPaneButton,
PropertyPaneToggle
} from
'@microsoft/sp-webpart-base' From there, we can start expanding the code in the getPropertyPaneConfiguration() method. We saw how to divide up our properties into pages and groups in the earlier code sample, but to zoom in on that for a second, here’s a specific change to move from the default arrangement to something with a second group. So we would move from the default of this:
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
..to..
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
},
{
groupName: "COB settings",
groupFields: [
PropertyPaneTextField('query', {
label: “Query”
})
]
}
]
As with any JSON, you have to take care with closing brackets and braces! Note that for simplicity/illustration, you can see I’m not putting my strings such as the groupName and control label text into the separate strings class here – but in real life I recommend doing this to avoid magic strings littered through your code.
So, structuring your properties into pages and groups is pretty simple. Now let’s start looking at the individual controls – I’ll look at some here, and some in future articles.
Text box (PropertyPaneTextField)
Properties:
Property | Description |
label | Label shown next to the control. |
description | Description shown next to the control. |
value | Value in the field – can be used to set default text. |
ariaLabel | A non-visible label – used by accessibility-focused tools and browsers, as per the ARIA standards. |
multiline | Specifies whether the textbox is multiline (boolean). |
placeholder | Allows you to provide default placeholder text, shown when the control has no value. |
resizable | Specifies whether the control can be enlarged by the user (to make it easier to enter larger amounts of data). |
underlined | Whether or not the textfield is underlined. |
errorMessage | A static value for the error message – it’s not clear to me when you’d use this, because the error is always displayed. The onGetErrorMessage property is what you’d really use it seems.. |
onGetErrorMessage | Pointer to function to use for validation – returns either a string (containing the error message) for simple cases OR a Promise<string> for more complex cases where you need to make an async call to SharePoint (or similar) to do the validation. See below for more details. |
deferredValidationTime | Amount of time (milliseconds) to wait before showing the validation error message – e.g. to allow the user to finish entering a value. |
The code:
Note that I’m also showing a simple textbox validation routine in the code below – note the onGetErrorMessage usage:
PropertyPaneTextField('textboxProperty', {
label: 'This is the label',
multiline: true,
resizable: true,
onGetErrorMessage: this.simpleTextBoxValidationMethod,
errorMessage: "This is the error message",
deferredValidationTime: 5000,
placeholder: "This is the placeholder text (shown when no value is entered)",
"description": "This is the description"
})
private simpleTextBoxValidationMethod(value: string): string {
if (value.length < 5) {
return "Value must be more than 5 characters!";
} else {
return "";
}
}
What it looks like:
..and showing a more real-life example once the validation function has executed:
More advanced validation for the text box control
So in the example above, I showed a simple function to check the value the user entered was more than 5 characters. But what if you want to talk to SharePoint, or make some other async call to validate text box contents? In this case our function referenced in onGetErrorMessage must return a Promise<string> rather than a string – and things are a little more complex due to needing the right context for ‘this’ in your function. Here’s an example of an async textbox validation method – I’m checking if the value entered matches a list in the current site:
But there’s more. Initially I was getting errors indicating that ‘this’ was undefined in my function – manifesting in a ”Uncaught TypeError: Cannot read property 'context' of undefined” error as I was trying to access this.context in my web part code. So why was ‘this’ not populated? The answer is that it just isn’t with a default function pointer here. To get around this, we need to use the JavaScript/TypeScript ‘bind’ method. This allows a new invocation of the function where we can pass ‘this’ as a parameter – the receiving function then has the context. So where we reference our function in the onGetErrorMessage property of the textbox, instead of this:
onGetErrorMessage: this.asyncTextBoxValidationMethod,
..you need this:
onGetErrorMessage: this.asyncTextBoxValidationMethod.bind(this),
Big props to my colleague and friend Vardhaman for this – he has a great article on this specific subject (before the official docs mentioned it), and it’s there that I discovered this solution. Thanks Vard!
Checkbox (PropertyPaneCheckbox)
Properties:
Property | Description |
text | Text displayed next to the checkbox. |
checked | Specifies if the control is checked. |
disabled | Specifies if the control is disabled. |
Code:
PropertyPaneCheckbox('checkboxProperty', {
text: 'This is the text',
checked: true,
disabled: false
})
What it looks like:
Dropdown (PropertyPaneDropdown)
Properties:
Property | Description |
label | Label displayed next to the control. |
options | An array of IPropertyPaneDropdownOption objects (similar to HTML <option> tag – defines the key/text etc. of the option). Can be specified in a static way, or fetched dynamically – see my other post (coming soon!) for details on this. |
selectedKey | Key name of the selected item. Can be used to set the initially-selected item. |
disabled | Specifies if the control is *disabled* (not sure why this is flipped the other way compared to other controls! Hopefully this will be made consistent in future releases..) |
Code:
PropertyPaneDropdown('dropdownProperty', {
label: 'This is the label',
disabled: false,
options: [
{ key: 'Red', text: 'Red' },
{ key: 'Green', text: 'Green' },
{ key: 'DarkBlue', text: 'Dark blue' }
]
})
What it looks like:
Summary
Building web parts in the SharePoint Framework brings a few new things, including a new model for web part properties. This is where the framework shines compared to other techniques like using a Script Editor web part and a separate JavaScript file – that method simply did not provide a way to use custom properties. In this post we looked at the first few of the controls, and also noted that populating a dropdown dynamically needs a specific approach which I detail in another article.
In future articles, I’ll look at other out-of-the-box web part property controls.