Adventures with the SharePoint Framework (SPFx): Part 6 – Web Part Properties

SPFXBrandingAppTileIn order for web parts to function as intended there is usually a requirement to present the user with a number of web part properties that can be configured as parameters that govern how the web part operates. These web part properties can store explicit data values such as text strings, Boolean values, dates and numbers or they can store references (URLs) to where data is stored. These properties are therefore most commonly used to specify what data is to be consumed by the web part and how that data is presented to users. This is not always the case though, as a web part could provide a control such as a button to allow a user to start some kind of process but even then, it is likely that there will be need for custom web part properties to provide configuration settings or operating parameters.

In this article I am going to walk though how we can configure a web part to expose web part properties for configuration by users but before we can do that we need to get our heads around TypeScript.

TypeScript

If you want a full and detailed explanation of what TypeScript is then head on over to https://www.typescriptlang.org/. The tag line on this page sort of sums it up quite nicely I feel, so I took a screenshot.

TypeScript is really a facade to make up for the woeful inadequacies of JavaScript that somehow end up being something that it was never intended to by and that is – driver of the Internet!

The reason why JavaScript has been so successful is mainly because it offers true cross platform capabilities, not being tied to one particular product stack, browser or technology.

The reasons why JavaScript is woefully inadequate are way too many for me to go into in any depth in this article but here are a few thoughts:

  • It is a weakly-typed dynamic environment which means that debugging is difficult.
  • If debugging is difficult then code will tend to be buggy!
  • Whilst it is now an object-based language it’s still not a proper object-orientated programming language in that it still struggles to support key OOP development concepts such as encapsulation, inheritance and polymorphism.
  • It doesn’t scale very well. It was really designed for applications with a few hundred lines of code at most and not for solutions with 10s’ of thousands of lines of code – which is what we have out there today.

In case you think these are the lunatic rantings of C# developer with sour grapes I would suggest that you watch the first 5 minutes of Anders Hejlsberg’s presentation at Microsoft Build 2017, where he explains why there was a need to bring some structure and rigour to the development process.

So, TypeScript is a meta-programming language. It is a structure development framework that, in the end, spits out JavaScript in a form that the world’s browsers can cope with.  I am not setting out here to provide you with a tutorial on TypeScript but you need to be aware that TypeScript is woven into the fabric of the SPFx and you have to get to grips with it – simple as!

Actually, it’s a huge step forward for JavaScript development and I for one am hugely grateful for its insistence as it brings some rigour to the development process. It’s still nowhere near as good as C# though

The Web Part Class

Let’s take a look at the TypeScript file in which the web part class and all its associated methods and properties are defined. Here I shall carry on using the Test Web Part that we have been using for demonstration purposes in parts 1 to 4 of this series and so the .ts (TypeScript files has a .ts extension) file we are looking at sits in the src/webparts/{solution name}/{web part name}.ts file, in our case this file is called TestPartWebPart.ts and the class that defines our web part is called TestPartWebPart.

In the screenshot below, you can see the outline of the web part class.

The TestPartWebPart class extends the BaseClientSideWebPart and is instantiated with an ITestPartWebPartProps interface. This interface is where we define the properties that we will need in order for the web part to function as per our design. For now, it only comes with a single description property which is of the type string, but we’re soon going to change that.

The public render() method is where the web part generates the resulting output which might be Controls rendered from some JavaScript framework such as React or Knockout or it might just be plain old HTML.

The protected dataVersion() method returns the version information for the web part which can be set from inside the method.

In this post I am not looking at either of these methods but instead I will focus on the protected getPropertyPaneConfiguration() method.

The getPropertyPaneConfiguration() method

The screenshot below shows the default getPropertyPaneConfiguration () method which is responsible for defining the controls that allow for the configuration of the web part.

This method returns an array of pages. Pages contain Headers and Groups and a Group contains one or more field controls.

If you look closely at this code you will see that:

  • The Header has a description property
  • The Group has a groupName property
  • The PropertyPaneTextField control has a label property

These are all just text strings that get displayed in the UI and used to indicate the purpose and context of the Page, Group and field controls. As these properties are expecting text strings we could just replace them with static string values and be done with it:

Which would render like this in the workbench:

But that’s not how Microsoft has set things up for us, which leads me to conclude that this approach is not considered best practice.

Instead, what they have done is separated out these string values much in the same way that we might use constants in C# but the rationale goes beyond that because the approach used is intended to support localisation. In other words, if we do things the Microsoft way, it gives us the option to make our web part multi-lingual. After all English is not the only language in the world. So, let’s take a look at the approach that we are being steered towards.

A handy feature of the strongly typed world in which we now find ourselves with TypeScript is that we can right-click an object or element and navigate or peek at its definition.

If you navigate to the definition of these strings then you get sent to the mystrings.d.ts file, the contents of which is shown below:

This file declares an interface in which we can clearly see the named elements that we are referencing in the original getPropertyPaneConfiguration() method.

It also declares a module with a constant called strings which gives an instance of the interface which is exported. This is how we can use the dot notation to retrieve the values we are after, specifically:

  • strings.PropertyPaneDescription
  • strings.BasicGroupName
  • strings.DescriptionFieldLabel

But how are we able to reference these elements from the TestPartWebPart.ts? That’s achieved by the following highlighted import statement in the file:

Ok, so now we have defined the strings object by importing the TestPartWebPartStrings module residing in the mystrings.d.ts file. So, it is this strings object that allows us to use the dot notation to retrieve the values we are after. But where are the actual string values?

Well it turns out that this is where the support for globalization comes in. I’m going to leave globalization for another day, mainly because I’m not yet fully across how it works in the SPFx yet (there’s honesty for you) but the actual text strings are defined in the en-us.js file which sits alongside the mystrings.d.ts file in the loc sub-folder of the web part.

To get custom strings into our web part solution, this then is the place from which we must start.

I’m working my way up to demonstrating most of the standard field controls at our disposal and so I have updated this anonymous function to define a set of my own property-value mappings.

I now have to go back to the mystrings.d.ts file and update the ITestPartWebPartStrings interface.

Then back to the TestPartWebPart.ts file where we can now see by the red line underscoring that TypeScript no longer likes our default strings because they don’t exist anymore. A huge win for strong typing!

Importing Field Controls

The default web part only provided us with a single text property called description and to provide us with a field control the TestPartWebPart.ts file only imported the PropertyPaneTextField control because that was that was needed. However, I want to see what the other field controls look like and so that means I have to import them.

Notice how TypeScript grey’s out the classes I just imported. That’s its way of telling me that I’m not using any of them yet.

Adding new Pages, Groups and Field Controls

As the intention here is to investigate what all these field controls look like and how they operate, we need to create a sample set of them in the getPropertyPaneConfiguration() method.

However, if you look back at how the field control of the textbox that came with the web part by default we can see that the first parameter in the control constructor method is a text string:

This ‘description’ is a reference to a property of the web part defined in interface that was used when we instantiated the class.

What we have to do here then, is create an interface property for every field control that requires data to be maintained by the web part which is nearly all of them. The above therefore becomes:

I’ve removed description property as we don’t need it any more but in the render method() which I’m not going into detail in this article, I did update the output to show the singleLineTextField property in its place

Next we have to create the Pages, Groups and Field Controls that can connect to the above web part properties.

In my implementation I configured my field controls to be presented in 3 different groups over a couple of pages.

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {          
          displayGroupsAsAccordion: true,        
          header: {               
            description: strings.DescriptionHeader1,              
          },
          groups: [            
            {              
              groupName: strings.GroupNameA, 
              isCollapsed: true,
              groupFields: [                            
                PropertyPaneHorizontalRule(),
                PropertyPaneTextField('singleLineTextField', {
                  label: strings.FieldLabelTextbox
                }),
                PropertyPaneCheckbox('checkBoxField',{
                  text: strings.FieldLabelCheckbox
                }),                
                PropertyPaneDropdown('dropdownField', {
                  label: strings.FieldLabelDropdown, 
                  options: [ 
                    {key: 1, text: 'Choice 1'},
                    {key: 2, text: 'Choice 2 (default)'},
                    {key: 3, text: 'Choice 3'}
                  ], selectedKey: 2
                })               
              ]
            },
            {              
              groupName: strings.GroupNameB,
              isCollapsed: true,
              groupFields: [                            
                PropertyPaneHorizontalRule(),
                PropertyPaneChoiceGroup('radioButtonField', {
                  label: strings.FieldLabelChoiceGroup,                  
                  options: [
                    {key: 1, text: "Radio Choice 1"},
                    {key: 2, text: 'Radio Choice 2', checked : true},
                    {key: 3, text: 'Radio Choice 3'}
                  ]
                }),               
                PropertyPaneTextField("multiLineTextField",{
                  label : strings.FieldLabelMultilineTextbox,
                  multiline : true,
                  placeholder : 'Add some text here',
                  resizable: true,
                  description: 'You can place any text you like in here'
                }),
                PropertyPaneLabel("labelField", {
                  text : strings.FieldLabelLabel
                })
              ]
            }                        
          ]
        },
        {           
          header: {               
            description: strings.DescriptionHeader2           
          },
          groups: [
            {              
              groupName: strings.GroupNameC,
              groupFields: [                            
                PropertyPaneHorizontalRule(),
                PropertyPaneLink('linkField', {                                   
                  text: "Kaboodle Software",
                  href: "https://kaboodlesoftware.com", 
                  target: "_blank"                  
                }),
                PropertyPaneSlider('sliderField', {
                  label: strings.FieldLabelSlider,
                  min: 1, max: 20, step: 1, value: 5, showValue:true
                }),
               PropertyPaneToggle('toggleField',{
                 label :strings.FieldLabelToggle,
                 key: 'OnOrOff',
                 onText : 'Enabled',
                 offText: 'Disabled'
               })               
              ]              
            }                        
          ]
        }
      ]
    };
  }

This implementation of the getPropertyPaneConfiguration() is not intended to achieve anything other than show case some of the field controls are at our disposal and few useful configuration settings that can be applied. How you arrange the pages, groups and field controls and use them is of course up to you.

After a gulp serve, you can add a web part instance to the local workbench and edit the web part properties to see the output.

I am not going to write up a detailed account of how to configure each field control as that work has already been done by others, most notably by SharePoint legend, Chris O’Brien in a couple of posts:

Welcome Improvements, Missing Controls and Missing Tricks

There is no doubt that there is a wealth of improvements to many of the editor controls. With a modicum of effort, we can do some validation on text entries (see the first post linked above) and we now have buttons and links to play with which we never had before and some controls support custom graphics. Check out the Choice Group example that’s provided by Chris in the 2nd of the above linked articles, it’s a really cool demo of can now be done with hardly any effort at all.

The interface for the page header enticingly provides an image property.

But sadly, no amount of persuasion could get this to work for me. If someone out there knows the circumstances or constraints under which the property functions then I would be delighted to hear about it.

Pages is something new that we never had in the Classic world and they are a welcome addition. I have taken to adding version information (version number and release date) as non-editable properties of my web parts and with the new Link control I can now add links to support pages, help and user guides etc., which is all very useful stuff. My plan in this new world order is to set aside a second page to contain this supporting information but keep the primary configuration settings on the first page.

Pages also have the option to display groups in expanding sections by setting the displayGroupsAsAccordian property to true. You can then set the isCollapsed property of each group to specify whether the group should initially be expanded or collapsed. By default, isCollapsed is set to false so the group will be expanded.

Leading to:

Which is much more in keeping with what we have in Classic Mode.

Because the displayGroupsAsAccordian is a property of the page then we can have some pages with expanding sections and others without, if we like.

Rich as they are, the standard set of field editors are missing a few capabilities that I really would have hoped for, if not expected. First up, there is no date picker or time picker controls. This is such a common requirement that I can’t quite understand why it’s been left out in the cold. The same goes for a multi-choice picker, usually provided as some kind of checkbox list control.

Nice to haves, would have been a User/Group picker and a term picker into the Managed Metadata Services (MMS) and how about a Browse button that would allow us to select a file from somewhere. I know that none of these were standard offerings in the Classic world but that doesn’t mean that they wouldn’t be useful.

I know that there is the option to create our own custom property editor controls but that involves quite a lot of hard work. It is a perfectly sensible option if you have some unique requirements but I hardly think that a date picker constitutes a unique requirement!

One thing I am sure that I will miss is that ability to just let SharePoint figure out what controls should be used for which web part properties. In the Classic world, if I declared a Boolean property or a String property or (best of all) any random Enumeration type then SharePoint would give be a checkbox, a textbox or a dropdown automatically, all magically configured and working. And if your String property need more text than could easily be added in a single line then there was a native option to pop-up a dialog that gave you more room to play with. Only if you needed something special did you need to resort to building custom tool parts.

It’s not that it’s hard to declare field controls and hook them up to web part properties it just involves more effort and more code that we might be used to.

And finally, a consequence of defining everything in JSON is that it is incredibly tiresome to get your opening a closing brackets ([{}]) all synchronised! TypeScript helps a lot here but we never had this much trouble in C# – not much I can do about that, ho hum

Reactive or Non-Reactive

By default, the property pane is Reactive meaning that whenever a property is updated in the web part the change is immediately applied and the effect on the output from the web part (should there be any) should be immediately apparent. You can see this with the description property that comes with the standard web part provided by Yeoman. The value in the linked field control is funnelled to the web part’s Render() method so editing the property immediately changes what is shown in the web part rendering space.

As mentioned above, in my example web part, I renamed the description property to the singleLineTextField property but the effect is the same. How the content is rendered using web part properties is the subject of the next article in this series but that’s not what I am focusing on here.

Notice that we have no Save or Apply buttons anymore and this is because we don’t need them when in Reactive mode.

Actually, that’s not strictly true, we don’t have these buttons on a Modern page but if we were adding the web part to a Classic page then whilst we can access the Modern property pane by clicking the Configure button, when we are done setting our properties and close down the property pane, we still have click the Classic OK or Apply to make the new property values stick.

That one had me a bit confused for a while because your natural tendency is to click the Save button in the ribbon after you change property values, after all you can see its updated in the UI.

The only problem is, if you do this then your changes won’t stick. Instead, what you need to do is make you changes, close down the pane and then click Ok or Apply in the old UI. Then you can save the page and all is good.

There might be occasions when you do not want your web part properties to be Reactive and you can make the change by overriding the disableReactivePropertyChanges() method in your web part class so that now returns true, as shown below:

protected get disableReactivePropertyChanges(): boolean { 
	return true; 
}

When you then gulp
serve the solution you will see that your web part miraculously acquires an Apply button.

Now when we enter some text in the textbox linked to the web part’s render method we don’t see the changes applied instantly. Instead, the changes only get set after we click the Apply button.

Notice that after we click Apply the button is disabled and greyed out, letting us know that there is nothing to update, but as soon as we change a setting in one of the controls the Apply button lights up again. That’s kinda cool.

Note that the web part’s Reactive or Non-Reactive mode is a global setting. Its all or nothing, so you can’t have a one Page or Group as Reactive whilst others are Non-Reactive. I guess that make sense because you wouldn’t want multiple Apply buttons all over the place.

So which mode is best? Sure, Reactive is less effort but sometimes I do stupid things in my web part properties and want to back out from my changes and Non-Reactive mode gives us that choice. Also, web part settings don’t get changed all that often and users coming from a Classic world will be comfortable enough dealing with an Apply button. Indeed, without one they might well be wondering how to persist the changes they have made, blissfully unaware that it’s already happened.

On the downside, the Classic experience is actually worse in Non-Reactive mode because although the Apply button updates the property value it still doesn’t actually persist the change i.e. you still have to close down the modern property pane and click the OK or Apply buttons in the Classic UI of the web part to make your properties stick. Having 2 Apply buttons will surely cause confusion.

On balance I think I will be using the default Reactive mode wherever I can and only resort to Non-Reactive mode I have no other choice.

Conclusions and Recommendations

The key take-aways are:

  • TypeScript is woven into the fabric of the SPFx and provides a strongly typed object orientated abstraction layer around JavaScript.
  • TypeScript may be a steep learning curve if you know is JavaScript development and the concepts of a strong typing and other key principles of object-orientated design are alien to you. But TypeScript is essential if we are going to build anything that approaches robust and scalable code in this new world order. If you are developing against the SPFx then you can’t avoid TypeScript so toughen up princess and get with the program.
  • When creating web parts Microsoft provide us with a best practice way of setting text values to be shown in the web part UI that is designed to make support for globalisation much simpler. This approach is of course more cumbersome to deal with than just providing static string values but that doesn’t mean you should take the easy way out! If your solution is a rip-roaring success and you decide that you need it to support Spanish, French or German then adding this capability will be so much easier if you start out doing things in the prescribed way, even if it takes more effort up front.
  • The nested container structure of Pages and Groups now make it easier to organise the field controls into a configuration interface that is contemporary and easy to work with. In particular, we never had the option of multiple pages in the Classic world and this is a welcome addition.
  • We can still make control groups expandable and collapsible if we like, but this is now an option. In Classic everything was in an expanding group, like it or not.
  • If we do decide to use expandable groups then we can decide whether the group is initial expanded or collapsed which we couldn’t do with custom groups in Classic mode, as they were always collapsed initially.
  • Unlike with Classic web parts where you get a starter set of web part properties and standard control groups, in the Modern world we get nothing. The start point is a blank canvas and its entirely up to us as developers to figure out what should go where.
  • Gone is automated generation of standard property editor controls based on the type of the web part property that is being surfaced. Now we have to hook everything up manually, but the SPFx does provide us with a rich starter set of field controls and it is quite straight forward to hook these controls up to web part properties.
  • We get some welcome new additions to the set of field controls like the toggle, slider and link controls but there are some missing controls that really should be included as standard, most notably a date/time picker and a multi-item picker such as a checkbox list control.
  • Although we can always create custom field controls ourselves, this is not a trivial exercise and it would be great to see some additional controls, such as a People/Group picker and a term picker for the MMS were included in the standard set.
  • The SPFx allows us to control whether our field controls are Reactive, where the property gets updated immediately when it is changed by the user, or Non-Reactive, where the property changes get set after an Apply button is clicked. Neither experience is particularly good if your web parts are being inserted on a Classic page as it’s not obvious that you have to click the Classic Ok or Apply buttons to make sure that the property settings are persisted. Because the experience is actually worse in Classic mode for Non-Reactive web parts, resulting in users being presented with 2 different Apply buttons that don’t do the same thing, then I recommend adopting a Reactive mode where possible.
  • Personally, I get really frustrated with the challenge of bracket matching when defining everything as JSON objects but as time goes by I guess I’ll get used to it.

In this article we have looked at web part properties and how they can be hooked up to field controls so that the users of your web parts can apply an appropriate set of configuration values which can be used to govern how the web part operates and functions. The only major block still missing is how we can leverage those property settings to control what is rendered by the web part. We’ll take a look at that in the next acritical in this series.

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s