Property Pane dynamic fields



In most cases, we need to dynamically build the Property Pane based on some logics. Let's say we want to show some options only if some other property is chosen. In this article I show how to append a group of properties based on some logics.

Let's say we want to show some information of a company's members. We want to choose a department, then a group of members that belong to that department and choose some of them's location.

We can build a Property Pane Group that dynamically changes it's content.




In this example, there is a function that returns an IPropertyPaneGroup object. That object contains an array of IPropertyPaneField objects. Inside the function we can implement all the logics to decide which fields we want to show and it's behaviours.


In a helloworld project, we create the following function:


private getConditionalGroup() : IPropertyPaneGroup{

    let groupFields : Array<IPropertyPaneField<any>> = new Array<IPropertyPaneField<any>>();
    let group : IPropertyPaneGroup = {
      groupName: "Conditional Properties",
      groupFields: groupFields,
      isCollapsed: false,
      isGroupNameHidden: false
    }

    // push fields to groupFields array based on some logic

    let departmentChoice : IPropertyPaneField<IPropertyPaneDropdownProps> = PropertyPaneDropdown ("department", {
      label: "Department",
      options : [
        {key: Department.Sales, text:"Sales"},
        {key: Department.Financial, text:"Financial"}
      ]
    });
    groupFields.push(departmentChoice);

    // the second dropdown will be based on the first one's choice

    if (this.properties.department){

      if (this.properties.department == Department.Sales){

        let salesChoice : IPropertyPaneField<IPropertyPaneDropdownProps> = PropertyPaneDropdown ("salesMember", {
          label: "Members",
          options : [
            {key: SalesMember.Marketing, text:"Marketing"},
            {key: SalesMember.SalesMan, text:"SalesMan"}
          ]
        });
        groupFields.push(salesChoice);

        // a third dropdown will be shown only if the department is sales and the members are salesman

        if (this.properties.salesMember == SalesMember.SalesMan){
          let locationChoice : IPropertyPaneField<IPropertyPaneDropdownProps> = PropertyPaneDropdown ("location", {
            label: "Location",
            options : [
              {key: Location.Headquarters, text:"Headquarters"},
              {key: Location.Store, text:"Store"}
            ]
          });
          groupFields.push(locationChoice);
        }

      }else if (this.properties.department == Department.Financial){

        let financialChoice : IPropertyPaneField<IPropertyPaneDropdownProps> = PropertyPaneDropdown ("financialMember", {
          label: "Members",
          options : [
            {key: FinancialMember.Chief, text:"Chief"},
            {key: FinancialMember.Secretary, text:"Secretary"}
          ]
        });
        groupFields.push(financialChoice);

      }

    }

    return group;
  }


The above function appends to the groupFields array the properties we want to show based on the chosen department and the chosen group of members. The first dropdown shows the departments, the second dropdown shows the members of that department and a third dropdown shows the location of some of them.

In the getPropertyPaneConfiguration, we only need to call the function to render the entire group:


 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('description', {
                  label: strings.DescriptionFieldLabel
                })
              ]
            },
              this.getConditionalGroup()
          ]
        }
      ]
    };
  }


We define some enums for the dropdown keys, define the properties object and change the render function to show the chosen properties:


export enum Department {Sales=1, Financial=2};
export enum SalesMember { Marketing=1, SalesMan = 2};
export enum FinancialMember { Chief=1, Secretary = 2};
export enum Location { Headquarters=1, Store=2 }

export interface IConditionalPropertyWebPartProps {
  description: string;
  department: Department,
  salesMember: SalesMember,
  financialMember: FinancialMember
  location : Location
}





  public render(): void {
    let html = `
      <div class="${ styles.conditionalProperty }">
        <div class="${ styles.container }">
          <div class="${ styles.row }">
            <div class="${ styles.column }">
              <span class="${ styles.title }">Welcome to SharePoint!</span>
              <p class="${ styles.subTitle }">Customize SharePoint experiences using Web Parts.</p>
              <p class="${ styles.description }">${escape(this.properties.description)}</p>`;
              if (this.properties.department){
                html += `<p class="${ styles.description}"> Department: ${Department[this.properties.department]}</p>`;
              }
              if (this.properties.department==Department.Financial && this.properties.financialMember){
                html += `<p class="${ styles.description}"> Members: ${FinancialMember[this.properties.financialMember]}</p>`;
              }else if ( this.properties.department==Department.Sales && this.properties.salesMember){
                html += `<p class="${ styles.description}"> Members: ${SalesMember[this.properties.salesMember]}</p>`;
                if (this.properties.salesMember == SalesMember.SalesMan && this.properties.location){
                  html += `<p class="${ styles.description}"> Location: ${Location[this.properties.location]}</p>`;
                }
              }
              html +=`
              <a href="https://aka.ms/spfx" class="${ styles.button }">
                <span class="${ styles.label }">Learn more</span>
              </a>
            </div>
          </div>
        </div>
      </div>`;

      this.domElement.innerHTML=html;
  }



Conclusion

The PropertyPane object is composed by Pages, each Page is composed by Groups and a Group is composed by an array of Fields. Just put the logics outside the getPropertyPaneConfiguration function, and you can build your Pages, Groups and Fields.

Every time a property changes, the entire PropertyPane redraws itself and you can access the new properties values to implement all the logics on how the PropertyPane should render again.

Comments

Popular posts from this blog

Handling theme changes on a MS Teams Tab WebPart

Sharing Dynamic Data between WebParts