Building Custom Property for PropertyPane in SPFx

Working with SharePoint PropertyPane can be a cumbersome task as we only have a handful of controls which can be used. After struggling for a month, I decided to create several re-usable controls for ProperyPane.

Here is the link to git repo where i’ll be adding more custom properties for PropertyPane https://github.com/anchit1501/spfx-propertypane-controls

ColorPicker is one of the easiest control which helped me with deeper understanding of how controls work in SharePoint PropertyPane.

Image for post
Image for post

Let’s start by creating a HelloWorld webpart where we will create and integrate our ColorPicker and use it to control color in our webpart.

Image for post
Image for post

Now we need to create a .tsx file where we will be adding our UI and functionality for ColorPicker. It is going to be a class component which will will receive arguments for props and states.

import React from 'react';
import { createRef } from "office-ui-fabric-react/lib/Utilities";
export default class ColorPickerControl extends React.Component<IColorPickerControlProps,IColorPickerControlState>
{
private menuButtonElement = createRef<HTMLElement>();
public render(): React.ReactElement<IColorPickerControlProps> {return (<div/>);
}
}

We will use State to manage Callout and color in our ColorPicker control. Whereas props will be used to pass values and functionality into our webpart.

Now declare the props and states outside our class

export interface IColorPickerControlProps {
label: string;
color: string;
onColorChanged: (color: string) => void;
}
export interface IColorPickerControlState {
color: string;
isCalloutVisible: boolean;
}

Let us now import few things which we will be using in our property.

import { ColorPicker } from "office-ui-fabric-react/lib/components/ColorPicker";
import { Callout } from "office-ui-fabric-react/lib/Callout";

I am using office-ui-fabric ColorPicker and integrating it with Callout to give it a popup experience.

It’s time to create functions which will handle the Callout’s visibility and handle the color change.

// handles color change
public colorChanged(color: string) {
this.props.onColorChanged(color);
this.setState({
color
});
}
//handles state change to show and hide calloutpublic setCalloutVisible() {
this.setState({
isCalloutVisible: true
});
}
public dismissCallout() {
this.setState({
isCalloutVisible: !this.state.isCalloutVisible
});
}

Initialize the constructor and bind our functions in it, also declare the state in which we will set initial values for color and isCalloutVisible.

constructor(props: IColorPickerControlProps) {
super(props);
//Bind the current object to the external called method
this.colorChanged = this.colorChanged.bind(this);
this.setCalloutVisible = this.setCalloutVisible.bind(this);
this.dismissCallout = this.dismissCallout.bind(this);
//Initialize the statethis.state = {
color: props.color,
isCalloutVisible: false};
}

Let’s add a little css to our ColorPicker. We are going to put this inside render method

const miniButtonStyle = {
width: "40px",
height: "20px",
padding: "6px",
backgroundColor: this.state.color,
borderRadius: "5px",
boxShadow: "2px 2px 2px black"
};

Now we will add the HTML for our property. Create a div which will act as a button for opening Callout which contains ColorPicker. Bind the setCalloutVisible with onClick function on div which will display the Callout.

We also need to bind colorChanged function to update the state and dismissCallout function to dismiss the Callout.

<div>
<label style={{ fontWeight: 600 }}>{this.props.label}</label>
<div
ref={this.menuButtonElement}
style={miniButtonStyle}
onClick={this.setCalloutVisible}
id="colorpicker"
/>
{this.state.isCalloutVisible && (
<Callout
className={"ms-CalloutExample-callout"}
gapSpace={0}
target={this.menuButtonElement.value}
setInitialFocus={true}
hidden={!this.state.isCalloutVisible}
onDismiss={this.dismissCallout}
>
<ColorPicker
color={this.state.color}
onColorChanged={e => this.colorChanged(e)}
alphaSliderHidden={true}
/>
</Callout>
)}
</div>

We are done with our UI and internal functionality of out property. Now we need create an interface which will bind our property with webpart where we intent to use it.

Create a new .ts file for passing our property as a control to the webpart’s PropertyPane.

import * as React from 'react';
import * as ReactDom from 'react-dom';
import {
IPropertyPaneField,PropertyPaneFieldType
} from '@microsoft/sp-webpart-base';
import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-webpart-base';import ColorPickerControl,{IColorPickerControlProps} from './ColorPickerControl';export interface ColorPickerControlInternalProps extends IPropertyPaneCustomFieldProps {
onRender: any;
label: string;
color: string;
onColorChanged: (color: string) => void;
}
export class ColorPickerControlProperty implements IPropertyPaneField<ColorPickerControlInternalProps> {
public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
public targetProperty: string;
public properties: ColorPickerControlInternalProps;
private elem: HTMLElement;
constructor(targetProperty: string, properties: ColorPickerControlInternalProps) {
this.targetProperty = targetProperty;
this.properties = {
key: properties.key,
label: properties.label,
color: properties.color,
onColorChanged: properties.onColorChanged,
onRender: this.onRender.bind(this)
};
}
public render(): void {
if (!this.elem) {return;}
this.onRender(this.elem);
}
private onRender(elem: HTMLElement): void {
if (!this.elem) {this.elem = elem;}
//Render the property in our PropertyPaneconst element: React.ReactElement<IColorPickerControlProps> = React.createElement(ColorPickerControl, {
key: this.properties.key,
label: this.properties.label,
color: this.properties.color,
onColorChanged: this.properties.onColorChanged
});
ReactDom.render(element, elem);
}
}

The main purpose of this file to export our custom code as a property for our Propertypane.

Props declared inside ColorPickerControlInternalProps will be used to pass values between webpart and property and the constructor is responsible for binding properties with our webpart.

Our ColorPicker is now ready. We just need to add it in our webpart and test it out.

Open webpart.manifest.json where you want to use the property and add a default value for the color.

"properties": {
"description": "colorPicker",
"color" : "#ffffff"
}

Let’s import our newly created property by importing its .ts file in our HelloWorldWebPart.ts

import {ColorPickerControlProperty} from '../../controls/ColorPicker/ColorPickerControlProperty';
import { update } from "@microsoft/sp-lodash-subset";

Add our property’s props(color,onColorChanged) in WebpartProps to build a two-way connection with our property.

export interface IHelloWorldWebPartProps {
description: string;
color: string;
onColorChanged: (color: string) => void;
}

Create a property which will pass value fromHwebpart’s property pane to the webpart as shown below.

public render(): void {
const element: React.ReactElement<IHelloWorldProps > = React.createElement(
HelloWorld,
{
description: this.properties.description,
color: this.properties.color
});
ReactDom.render(element, this.domElement);
}

Add the same to your webpart’s props file

export interface IHelloWorldProps {
description: string;
color: string;
}

Declare onColorChange function which will update our property for the webpart.

protected onColorChange(color: any) {
update(
this.properties,
"color",
(): any => {return color;}
);
this.render();
}

Add your newly created property in PropertyPaneConfiguration

new ColorPickerControlProperty("color", {
key: "COLOR PICKER",
label: "COLOR PICKER",
color: this.properties.color,
onColorChanged: this.onColorChange.bind(this),
onRender: this.render.bind(this)
})

Our Custom Property is bound with HelloWorld webpart. Let’s test it out by using it.

Open HelloWorld.tsx and link the property however you want. I am using it to change background color.

<div className={ styles.helloWorld }>
<div className={ styles.container }>
<div className={ styles.row } style={{backgroundColor : `${this.props.color}`}}>
<div className={ styles.column }>
<span className={ styles.title }>Welcome to SharePoint!</span>
<p className={ styles.subTitle }>Customize SharePoint experiences using Web Parts.</p><p className={ styles.description }>{escape(this.props.description)}</p><a href="https://aka.ms/spfx" className={ styles.button }>
<span className={ styles.label }>Learn more</span>
</a>
</div>
</div>
</div>
</div>

Now run the code.

Here is how our webpart will look with our porperty in PropertyPane.

Image for post
Image for post

You can use this method to create your own custom property in accordance to your needs.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store