Sp-dev-docs: Cascading dropdown Webpart properties are cleared after refresh

Created on 22 May 2019  路  6Comments  路  Source: SharePoint/sp-dev-docs

I am trying to follow the same steps here: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/use-cascading-dropdowns-in-web-part-properties

But I wanted to do it with Folders instead of items.

Here's the webpart class I have right now:

export default class FolderNavigator extends BaseClientSideWebPart<IFolderNavigatorWebPartProps> {
  private lists: IPropertyPaneDropdownOption[];
  private folders: IPropertyPaneDropdownOption[];
  private listsDropdownDisabled: boolean = true;
  private foldersDropdownDisabled: boolean = true;


  public onInit(): Promise<void> {
    return super.onInit().then(_ => {
      sp.setup({
        spfxContext: this.context
      })
    });
  }


  private async loadLists(): Promise<IPropertyPaneDropdownOption[]> {
    let listsRequest = sp.web.lists.filter('Hidden eq false').get()

    let lists = await listsRequest

    let options: IPropertyPaneDropdownOption[] = [];
    lists.forEach(list => {
      options.push({
        key: list.Title,
        text: list.Title
      })
    })

    return options;
  }


//Here everytime the property pane loads, this.lists and this.folders = undefined
  protected async onPropertyPaneConfigurationStart(): Promise<void> {
    this.listsDropdownDisabled = !this.lists;
    this.foldersDropdownDisabled = !this.folders;

    if (this.lists) {
      return;
    }

    this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'lists');

    let listOptions = await this.loadLists()

    this.lists = listOptions;
    this.listsDropdownDisabled = false;
    this.context.propertyPane.refresh();
    this.context.statusRenderer.clearLoadingIndicator(this.domElement);
    this.render();
  }

  private async loadAvailablefolders(listName: string): Promise<IPropertyPaneDropdownOption[]> {
    let availablefolders = sp.web.lists.getByTitle(listName).rootFolder.folders
      .filter('ListItemAllFields/Id ne null')
      .expand('ListItemAllFields')
      .get()

    let folders = await availablefolders

    let options: IPropertyPaneDropdownOption[] = []

    console.log(folders)
    folders.forEach(folder => {
      options.push({
        key: folder.Name, 
        text: folder.Name
      })
    })

    return options;
  }


  protected async onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): Promise<void> {
    if (propertyPath === 'listName' && newValue) {
      this.listsDropdownDisabled = true;
      let folders = await this.loadAvailablefolders(newValue)
      this.folders = folders
      this.foldersDropdownDisabled = false
      this.listsDropdownDisabled = false
      this.context.propertyPane.refresh();
      this.render()

    }
    super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);

  }


  public render(): void {
    const element: React.ReactElement<INavigatorProps> = React.createElement(
      Navigator,
      {
        description: this.properties.listName
      }
    );

    ReactDom.render(element, this.domElement);
  }

  protected onDispose(): void {
    ReactDom.unmountComponentAtNode(this.domElement);
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('listName', {
                  label: strings.ListNameLabel,
                  options: this.lists,
                  disabled: this.listsDropdownDisabled
                }),
                PropertyPaneDropdown('folderName', {
                  label: strings.FolderNameLabel,
                  options: this.folders,
                  disabled: this.foldersDropdownDisabled
                })
              ]
            }
          ]
        }
      ]
    };
  }
}

Whenever the function: onPropertyPaneConfigurationStart runs, this.lists and this.folders values are undefined

Category

  • [x] Question
  • [ ] Typo
  • [ ] Bug
  • [ ] Additional article idea
spfx-general answered by-design question

Most helpful comment

First, this is not necessary:

protected async onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): Promise<void> {
  ...
  this.properties[propertyPath] = newValue
  ...
}

I think you're doing this because you don't understand how public properties on the web part are declared & persisted... I'm making that assumption from when you said:

The examples I am reading online they show how to create the webpart property, but they don't show how to actually save the prop, and the values would be gone after the refresh.

SPFx automatically saves the value of the public property you define in the interface of the web part & when you bind a property pane control to that property.

How? It does that automatically because you've bound to it by name here:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
  return {
    pages: [
      {
        groups: [
          {
            groupFields: [
              PropertyPaneDropdown('listName', {..}),
              PropertyPaneDropdown('folderName', {..})
            ]
          }
        ]
      }
    ]
  };
}

_Actually..._ where's the interface that defines the public properties for your web part? That's usually in the same file & declared immediately above the web part declaration. That tells SharePoint what public properties are on your web part. You should have something like:

public interface IFolderNavigatorWebPartProps {
  listName: string;
  folderName: string;
}

Second... as for that article you're referencing, I don't see anything in it where it is demonstrating setting the initial state of the dropdown selectors. The project it references at the top is a more complex version of that article as it shows how to create custom property pane controls, but that's now what you're trying to do.

BTW... I wouldn't use the title as the key... use the list's ID. Someone can rename the list and the selections break for existing instances of the web parts, but the IDs never change.

All 6 comments

Thank you for reporting this issue. We will be triaging your incoming issue as soon as possible.

This is by design... it's because you aren't setting the values of the drop downs in the property pane. Look at the selectedKey property of the IPropertyPaneDropdownProps interface. This should be the key of the item you want to have selected.

@andrewconnell Thanks for your response. I tried to modify the property pane code to include the selectedkey property like this:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('listName', {
                  label: strings.ListNameLabel,
                  options: this.lists,
                  disabled: this.listsDropdownDisabled, 
                  selectedKey: this.properties.listName
                }),
                PropertyPaneDropdown('folderName', {
                  label: strings.FolderNameLabel,
                  options: this.folders,
                  disabled: this.foldersDropdownDisabled, 
                  selectedKey: this.properties.folderName
                })
              ]
            }
          ]
        }
      ]
    };
  }

and I updated the onPropertyPaneFieldChanged method to update these properties whenever I change the dropdown list value, like
this:

  protected async onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): Promise<void> {
    if (propertyPath === 'listName' && newValue) {
      this.listsDropdownDisabled = true;
      let folders = await this.loadAvailablefolders(newValue)
      this.folders = folders
      this.foldersDropdownDisabled = false
      this.listsDropdownDisabled = false
      this.properties[propertyPath] = newValue
      this.context.propertyPane.refresh();
      this.render()

    }
    super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);

  }

I am confused about two things, am I not following the steps here correctly: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/use-cascading-dropdowns-in-web-part-properties ?

Second thing is, when I change the selected item in a dropdown list, I save its value in this.properties[propName], doesn't this save to the selectedkey of the property according to my code?

The examples I am reading online they show how to create the webpart property, but they don't show how to actually save the prop, and the values would be gone after the refresh.

What am I doing wrong here?

First, this is not necessary:

protected async onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): Promise<void> {
  ...
  this.properties[propertyPath] = newValue
  ...
}

I think you're doing this because you don't understand how public properties on the web part are declared & persisted... I'm making that assumption from when you said:

The examples I am reading online they show how to create the webpart property, but they don't show how to actually save the prop, and the values would be gone after the refresh.

SPFx automatically saves the value of the public property you define in the interface of the web part & when you bind a property pane control to that property.

How? It does that automatically because you've bound to it by name here:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
  return {
    pages: [
      {
        groups: [
          {
            groupFields: [
              PropertyPaneDropdown('listName', {..}),
              PropertyPaneDropdown('folderName', {..})
            ]
          }
        ]
      }
    ]
  };
}

_Actually..._ where's the interface that defines the public properties for your web part? That's usually in the same file & declared immediately above the web part declaration. That tells SharePoint what public properties are on your web part. You should have something like:

public interface IFolderNavigatorWebPartProps {
  listName: string;
  folderName: string;
}

Second... as for that article you're referencing, I don't see anything in it where it is demonstrating setting the initial state of the dropdown selectors. The project it references at the top is a more complex version of that article as it shows how to create custom property pane controls, but that's now what you're trying to do.

BTW... I wouldn't use the title as the key... use the list's ID. Someone can rename the list and the selections break for existing instances of the web parts, but the IDs never change.

Thank you for clearing up the confusion. Now it's clear. Very much appreciated @andrewconnell

Issues that have been closed & had no follow-up activity for at least 7 days are automatically locked. Please refer to our wiki for more details, including how to remediate this action if you feel this was done prematurely or in error: Issue List: Our approach to locked issues

Was this page helpful?
0 / 5 - 0 ratings