Tuesday, 13 September 2016

Web part properties in the SharePoint Framework – part 1

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.

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:

SNAGHTML61509e7e

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:

SNAGHTML6152c7e6

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:

import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  IWebPartContext,
  PropertyPaneTextField
} from '@microsoft/sp-webpart-base'

..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:

SNAGHTML4d5ddb9f

..and showing a more real-life example once the validation function has executed:

SNAGHTML4df82741

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:

SNAGHTML4e0dbd03

 

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:

SNAGHTML5c0d42aa

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.

2 comments:

Elio Struyf said...

Great article like always. Another useful property you could set for groups is the displayGroupsAsAccordion. Which does what the name says, it shows each group in an accordion.

pages: [{
header: {
description: strings.PropertyPaneDescription
},
groups: [{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: strings.FieldsTitleLabel
})
]
}, {
groupName: strings.TemplateGroupName,
groupFields: [
PropertyPaneToggle('external', {
label: strings.FieldsExternalLabel
})
]
}],
displayGroupsAsAccordion: true
}]

Greets,
Elio

Chris O'Brien said...

@Elio,

Ooh, nice tip! Thanks mate..

COB.