Compartir a través de


Compilar controles personalizados para el panel de propiedades

SharePoint Framework contiene un conjunto de controles estándares para el panel de propiedades. Aun así, a veces es necesaria funcionalidad adicional más allá de los controles básicos. Podría necesitar actualizaciones asincrónicas de los datos en un control o una interfaz de usuario específica. Compile un control personalizado para el panel de propiedades para obtener la funcionalidad que necesita.

En este artículo, compilará un control desplegable personalizado que carga sus datos de forma asincrónica desde un servicio externo sin bloquear la interfaz de usuario del elemento web.

Menú desplegable de elementos en el que se cargan los elementos disponibles después de seleccionar una lista en el menú desplegable de lista

El origen del elemento web de trabajo está disponible en GitHub en sp-dev-fx-webparts/samples/react-custompropertypanecontrols/.

Nota:

Antes de seguir los pasos de este artículo, asegúrese de configurar el entorno de desarrollo para crear soluciones de SharePoint Framework.

Crear un proyecto

  1. Empiece por crear una carpeta para el proyecto:

    md react-custompropertypanecontrol
    
  2. Vaya a la carpeta del proyecto:

    cd react-custompropertypanecontrol
    
  3. En la carpeta del proyecto, ejecute el generador de Yeoman de SharePoint Framework para aplicar scaffolding a un nuevo proyecto de SharePoint Framework:

    yo @microsoft/sharepoint
    
  4. En el momento en que se le solicite, introduzca los siguientes valores (seleccione la opción predeterminada para todas las solicitudes que se omitan a continuación):

    • ¿Cuál es el tipo de componente del lado cliente que se va a crear? WebPart
    • ¿Cómo se llama su elemento web? Elementos de lista
    • ¿Qué plantilla desea usar? React
  5. Abra la carpeta del proyecto en el editor de código.

Definir la propiedad del elemento web para almacenar la lista seleccionada

El elemento web que está generando muestra los elementos de lista de la lista de SharePoint seleccionada. Los usuarios pueden seleccionar una lista en las propiedades del elemento web. Para almacenar la lista seleccionada, cree una propiedad de elemento web denominada listName.

  1. En el editor de código, abra el archivo src/webparts/listItems/ListItemsWebPartManifest.json. Reemplace la propiedad de description predeterminada por una nueva propiedad denominada listName:

    {
      ...
      "preconfiguredEntries": [{
        ...
        "properties": {
          "listName": ""
        }
      }]
    }
    
  2. Abra el archivo src/webparts/listItems/ListItemsWebPart.ts y actualice la interfaz IListItemsWebPartProps a lo siguiente:

    export interface IListItemsWebPartProps {
      description: string;
      listName: string;
    }
    
  3. En el archivo src/webparts/listItems/ListItemsWebPart.ts, cambie el método render() a:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      public render(): void {
        const element: React.ReactElement<IListItemsProps> = React.createElement(
          ListItems,
          {
            listName: this.properties.listName,
            description: this.properties.description,
            isDarkTheme: this._isDarkTheme,
            environmentMessage: this._environmentMessage,
            hasTeamsContext: !!this.context.sdks.microsoftTeams,
            userDisplayName: this.context.pageContext.user.displayName
          }
        );
    
        ReactDom.render(element, this.domElement);
      }
    
      // ...
    }
    
  4. Actualizar el método getPropertyPaneConfiguration() a:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
    
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    PropertyPaneTextField('listName', {
                      label: strings.ListFieldLabel
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    
      // ...
    }
    
  5. En el archivo src/webparts/listItems/loc/mystrings.d.ts, agregue una nueva propiedad ListFieldLabel de tipo string a la interfaz IListItemsWebPartStrings existente:

    declare interface IListItemsWebPartStrings {
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ..
      ListFieldLabel: string;
    }
    
  6. En el archivo src/webparts/listItems/loc/en-us.js, agregue una nueva propiedad ListFieldLabel al objeto devuelto:

    define([], function() {
      return {
        "PropertyPaneDescription": "Description",
        "BasicGroupName": "Group Name",
        ...
        "ListFieldLabel": "List"
      }
    });
    
  7. Abra el archivo src/webparts/listItems/components/IListItemsProps.ts y agregue la propiedad listName a la interfaz de lista:

    export interface IListItemsProps {
      description: string;
      isDarkTheme: boolean;
      environmentMessage: string;
      hasTeamsContext: boolean;
      userDisplayName: string;
      listName: string;
    }
    
  8. En el archivo src/webparts/listItems/components/ListItems.tsx, cambie los contenidos del método render() a:

    export default class ListItems extends React.Component<IListItemsProps, {}> {
      public render(): React.ReactElement<IListItemsProps> {
        const {
          description,
          isDarkTheme,
          environmentMessage,
          hasTeamsContext,
          userDisplayName,
          listName
        } = this.props;
    
        return (
          <section className={`${styles.listItems} ${hasTeamsContext ? styles.teams : ''}`}>
            <div className={styles.welcome}>
              <img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
              <h2>Well done, {escape(userDisplayName)}!</h2>
              <div>{environmentMessage}</div>
              <div>List name: <strong>{escape(listName)}</strong></div>
            </div>
          </section>
        );
      }
    }
    
  9. Ejecute el comando siguiente para comprobar que el proyecto se está ejecutando:

    gulp serve
    
  10. En el explorador web, agregue el elemento web Lista de elementos al lienzo y abra sus propiedades. Compruebe que el valor establecido para la propiedad List se muestra en el cuerpo del elemento web.

    Elemento web que muestra el valor de la propiedad

Crear un control de panel de propiedades desplegable asincrónico

SharePoint Framework ofrece un control desplegable estándar que permite a los usuarios seleccionar un valor específico. El control desplegable está construido de modo que requiere que se conozcan todos sus valores por adelantado. Si desea cargar los valores dinámicamente o está cargando valores de forma asincrónica desde un servicio externo y no quiere bloquear todo el elemento web, la creación de un control desplegable personalizado es una opción.

Al crear un control de panel de propiedades personalizado que utiliza React en SharePoint Framework, el control se compone de una clase que registra el control con el elemento web, y de un componente React que representa el desplegable y gestiona sus datos.

Agregar un componente React de control de panel de propiedades desplegable asincrónico

  1. Cree la carpeta de componentes. En la carpeta src del proyecto, cree una jerarquía de tres nuevas carpetas de modo que la estructura de carpetas sea src/controls/PropertyPaneAsyncDropdown/components.

    La carpeta Components resaltada en Visual Studio Code

  2. Defina las propiedades del componente React desplegable asincrónico. En la carpeta src/controls/PropertyPaneAsyncDropdown/components cree un archivo denominado IAsyncDropdownProps.ts y escriba el siguiente código:

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    
    export interface IAsyncDropdownProps {
      label: string;
      loadOptions: () => Promise<IDropdownOption[]>;
      onChanged: (option: IDropdownOption, index?: number) => void;
      selectedKey: string | number;
      disabled: boolean;
      stateKey: string;
    }
    

    La clase IAsyncDropdownProps define propiedades que pueden establecerse en el componente React usado por el control de panel de propiedades personalizado:

    • La propiedad label especifica la etiqueta del control desplegable.
    • El control llama a la función asociada al delegado loadOptions para cargar las opciones disponibles.
    • Cuando el usuario selecciona una opción del desplegable, se llama a la función asociada al delegado onChanged.
    • La propiedad selectedKey especifica el valor seleccionado, que puede ser una cadena o un número.
    • La propiedad disabled especifica si el control desplegable está deshabilitado o no.
    • La propiedad stateKey se usa para forzar el componente React que se va a volver a representar.
  3. Defina la interfaz del componente React desplegable asincrónico. En la carpeta src/controls/PropertyPaneAsyncDropdown/components cree un archivo denominado IAsyncDropdownState.ts y escriba el código siguiente:

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    
    export interface IAsyncDropdownState {
      loading: boolean;
      options: IDropdownOption[];
      error: string;
    }
    

    La interfaz IAsyncDropdownState describe el estado del componente React:

    • La propiedad loading determina si el componente está cargando sus opciones en el momento dado.
    • La propiedad options contiene todas las opciones disponibles.
    • Si se ha producido un error, se asigna a la propiedad error, desde donde se comunica al usuario.
  4. Defina el componente React desplegable asincrónico. En la carpeta src/controls/PropertyPaneAsyncDropdown/components, cree un archivo denominado AsyncDropdown.tsx y escriba el siguiente código:

    import * as React from 'react';
    import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    import { Spinner } from 'office-ui-fabric-react/lib/components/Spinner';
    import { IAsyncDropdownProps } from './IAsyncDropdownProps';
    import { IAsyncDropdownState } from './IAsyncDropdownState';
    
    export default class AsyncDropdown extends React.Component<IAsyncDropdownProps, IAsyncDropdownState> {
      private selectedKey: React.ReactText;
    
      constructor(props: IAsyncDropdownProps, state: IAsyncDropdownState) {
        super(props);
        this.selectedKey = props.selectedKey;
    
        this.state = {
          loading: false,
          options: undefined,
          error: undefined
        };
      }
    
      public componentDidMount(): void {
        this.loadOptions();
      }
    
      public componentDidUpdate(prevProps: IAsyncDropdownProps, prevState: IAsyncDropdownState): void {
        if (this.props.disabled !== prevProps.disabled ||
          this.props.stateKey !== prevProps.stateKey) {
          this.loadOptions();
        }
      }
    
      private loadOptions(): void {
        this.setState({
          loading: true,
          error: undefined,
          options: undefined
        });
    
        this.props.loadOptions()
          .then((options: IDropdownOption[]): void => {
            this.setState({
              loading: false,
              error: undefined,
              options: options
            });
          }, (error: any): void => {
            this.setState((prevState: IAsyncDropdownState, props: IAsyncDropdownProps): IAsyncDropdownState => {
              prevState.loading = false;
              prevState.error = error;
              return prevState;
            });
          });
      }
    
      public render(): JSX.Element {
        const loading: JSX.Element = this.state.loading ? <div><Spinner label={'Loading options...'} /></div> : <div />;
        const error: JSX.Element = this.state.error !== undefined ? <div className={'ms-TextField-errorMessage ms-u-slideDownIn20'}>Error while loading items: {this.state.error}</div> : <div />;
    
        return (
          <div>
            <Dropdown label={this.props.label}
              disabled={this.props.disabled || this.state.loading || this.state.error !== undefined}
              onChanged={this.onChanged.bind(this)}
              selectedKey={this.selectedKey}
              options={this.state.options} />
            {loading}
            {error}
          </div>
        );
      }
    
      private onChanged(option: IDropdownOption, index?: number): void {
        this.selectedKey = option.key;
        // reset previously selected options
        const options: IDropdownOption[] = this.state.options;
        options.forEach((o: IDropdownOption): void => {
          if (o.key !== option.key) {
            o.selected = false;
          }
        });
        this.setState((prevState: IAsyncDropdownState, props: IAsyncDropdownProps): IAsyncDropdownState => {
          prevState.options = options;
          return prevState;
        });
        if (this.props.onChanged) {
          this.props.onChanged(option, index);
        }
      }
    }
    

    La clase AsyncDropdown representa el componente React usado para representar el control de panel de propiedades desplegable asincrónico:

    • Cuando el componente se carga por primera vez, el método componentDidMount() o sus propiedades disabled o stateKey cambian, y cargan las opciones disponibles mediante una llamada al método loadOptions() pasado a través de las propiedades.
    • Una vez que se cargan las opciones, el componente actualiza su estado y muestra las opciones disponibles.
    • El desplegable se representa mediante el componente desplegable de Office UI Fabric React.
    • Cuando el componente está cargando las opciones disponibles, se muestra un indicador giratorio mediante el componente de indicador giratorio de Office UI Fabric React.

El siguiente paso es definir el control de panel de propiedades personalizado. Este control se usa dentro del elemento web al definir propiedades en el panel de propiedades y se representa mediante el componente React previamente definido.

Agregar un control de panel de propiedades desplegable asincrónico

  1. Defina las propiedades del control de panel de propiedades desplegable asincrónico. Un control de panel de propiedades personalizado tiene dos conjuntos de propiedades.

    El primer conjunto de propiedades se expone públicamente y se utiliza para definir la propiedad del elemento web dentro del elemento web. Estas propiedades son propiedades específicas de los componentes, como la etiqueta que se muestra al lado del control, los valores mínimos y máximos de un indicador giratorio, o las opciones disponibles para un desplegable. Cuando se define un control de panel de propiedades personalizado, se debe pasar el tipo que describe estas propiedades como el tipo TProperties al implementar la interfaz IPropertyPaneField<TProperties>.

    El segundo conjunto de propiedades son propiedades privadas que se utilizan internamente dentro del control de panel de propiedades personalizado. Estas propiedades deben adherirse a las API de SharePoint Framework para que el control personalizado se represente correctamente. Estas propiedades deben implementar la IPropertyPaneCustomFieldProps interfaz desde el paquete @microsoft/sp-property-pane .

  2. Defina las propiedades públicas del control de panel de propiedades desplegable asincrónico. En la carpeta src/controls/PropertyPaneAsyncDropdown, cree un archivo denominado IPropertyPaneAsyncDropdownProps.ts y escriba el siguiente código:

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    
    export interface IPropertyPaneAsyncDropdownProps {
      label: string;
      loadOptions: () => Promise<IDropdownOption[]>;
      onPropertyChange: (propertyPath: string, newValue: any) => void;
      selectedKey: string | number;
      disabled?: boolean;
    }
    

    En la interfaz IPropertyPaneAsyncDropdownProps:

    • label: define la etiqueta que aparece junto al desplegable.
    • loadOptions: define el método al que se llama para cargar las opciones del desplegable disponibles.
    • onPropertyChange: define un método al que se llama cuando el usuario selecciona un valor en la lista desplegable.
    • selectedKey: devuelve el valor del desplegable seleccionado.
    • disabled: especifica si el control está deshabilitado o no.
  3. Defina las propiedades internas del control de panel de propiedades desplegable asincrónico. En la carpeta src/controls/PropertyPaneAsyncDropdown, cree un archivo denominado IPropertyPaneAsyncDropdownInternalProps.ts y escriba el siguiente código:

    import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-property-pane';
    import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
    
    export interface IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps, IPropertyPaneCustomFieldProps {
    }
    

Aunque la interfaz IPropertyPaneAsyncDropdownInternalProps no define propiedades nuevas, combina las propiedades de la interfaz IPropertyPaneAsyncDropdownProps definida anteriormente y de la interfaz estándar IPropertyPaneCustomFieldProps de SharePoint Framework que es necesaria para que un control personalizado se ejecute correctamente.

  1. Defina el control de panel de propiedades desplegable asincrónico. En la carpeta src/controls/PropertyPaneAsyncDropdown, cree un archivo denominado PropertyPaneAsyncDropdown.ts y escriba el siguiente código:

    import * as React from 'react';
    import * as ReactDom from 'react-dom';
    import {
      IPropertyPaneField,
      PropertyPaneFieldType
    } from '@microsoft/sp-property-pane';
    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
    import { IPropertyPaneAsyncDropdownInternalProps } from './IPropertyPaneAsyncDropdownInternalProps';
    import AsyncDropdown from './components/AsyncDropdown';
    import { IAsyncDropdownProps } from './components/IAsyncDropdownProps';
    
    export class PropertyPaneAsyncDropdown implements IPropertyPaneField<IPropertyPaneAsyncDropdownProps> {
      public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
      public targetProperty: string;
      public properties: IPropertyPaneAsyncDropdownInternalProps;
      private elem: HTMLElement;
    
      constructor(targetProperty: string, properties: IPropertyPaneAsyncDropdownProps) {
        this.targetProperty = targetProperty;
        this.properties = {
          key: properties.label,
          label: properties.label,
          loadOptions: properties.loadOptions,
          onPropertyChange: properties.onPropertyChange,
          selectedKey: properties.selectedKey,
          disabled: properties.disabled,
          onRender: this.onRender.bind(this),
          onDispose: this.onDispose.bind(this)
        };
      }
    
      public render(): void {
        if (!this.elem) {
          return;
        }
    
        this.onRender(this.elem);
      }
    
      private onDispose(element: HTMLElement): void {
        ReactDom.unmountComponentAtNode(element);
      }
    
      private onRender(elem: HTMLElement): void {
        if (!this.elem) {
          this.elem = elem;
        }
    
        const element: React.ReactElement<IAsyncDropdownProps> = React.createElement(AsyncDropdown, {
          label: this.properties.label,
          loadOptions: this.properties.loadOptions,
          onChanged: this.onChanged.bind(this),
          selectedKey: this.properties.selectedKey,
          disabled: this.properties.disabled,
          // required to allow the component to be re-rendered by calling this.render() externally
          stateKey: new Date().toString()
        });
        ReactDom.render(element, elem);
      }
    
      private onChanged(option: IDropdownOption, index?: number): void {
        this.properties.onPropertyChange(this.targetProperty, option.key);
      }
    }
    

    La clase PropertyPaneAsyncDropdown implementa la interfaz estándar IPropertyPaneField de SharePoint Framework mediante la interfaz IPropertyPaneAsyncDropdownProps como un contrato para sus propiedades públicas que se puede establecer desde el elemento web. La clase contiene las tres propiedades públicas siguientes definidas por la interfaz IPropertyPaneField:

    • type: debe establecerse en PropertyPaneFieldType.Custom para un control de panel de propiedades personalizado.
    • targetProperty: se utiliza para especificar el nombre de la propiedad del elemento web que se utilizará con el control.
    • properties: se utiliza para definir propiedades específicas del control.

    Observe cómo la propiedad properties es del tipo de IPropertyPaneAsyncDropdownInternalProps interno en lugar de la interfaz de IPropertyPaneAsyncDropdownProps pública implementada por la clase. Esto se hace de forma intencionada para que la propiedad properties pueda definir el método onRender() requerido por SharePoint Framework. Si el método onRender() formaba parte de la interfaz pública IPropertyPaneAsyncDropdownProps, cuando use el control desplegable asincrónico en el elemento web, tendrá que asignarle un valor en el elemento web, lo cual no es deseable.

    La clase PropertyPaneAsyncDropdown define un método público render() que se puede usar para volver a dibujar el control. Esto es útil en situaciones tales como cuando haya desplegables en cascada donde el valor establecido en uno determina las opciones disponibles en otro. Si llama al método render() después de seleccionar un elemento, puede hacer que el desplegable dependiente cargue las opciones disponibles. Para que esto funcione, tiene que hacer que React detecte que el control ha cambiado. Esto se consigue mediante el establecimiento del valor de stateKey en la fecha actual. Gracias a este truco, cada vez que se llame al método onRender() el componente no solo se vuelve a representar, sino que también actualiza las opciones disponibles. No olvide implementar el método onDispose() para desmontar el elemento cada vez cierre el panel de propiedades.

Usar el control de panel de propiedades desplegable asincrónico en el elemento web

Con el control de panel de propiedades desplegable asincrónico listo, el siguiente paso es usarlo en el elemento web para permitir a los usuarios seleccionar una lista.

Agregar la interfaz de información de listas

Para pasar información sobre las listas disponibles de una manera coherente, defina una interfaz que represente la información sobre una lista. En la carpeta src/webparts/listItems, cree un archivo denominado IListInfo.ts y escriba el siguiente código:

export interface IListInfo {
  Id: string;
  Title: string;
}

Usar el control de panel de propiedades desplegable asincrónico para representar la propiedad de elemento web listName

  1. Haga referencia a los tipos necesarios. En la sección superior del archivo src/webparts/listItems/ListItemsWebPart.ts, importe la clase PropertyPaneAsyncDropdown creada anteriormente. Para ello, agregue lo siguiente:

    import { PropertyPaneAsyncDropdown } from '../../controls/PropertyPaneAsyncDropdown/PropertyPaneAsyncDropdown';
    
  2. Después de ese código, agregue una referencia a la interfaz IDropdownOption y dos funciones auxiliares necesarias para trabajar con propiedades de elementos web.

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    import { update, get } from '@microsoft/sp-lodash-subset';
    
  3. Agregue un método para cargar las listas disponibles. En la clase ListItemsWebPart, agregue el siguiente método loadLists() para cargar las listas disponibles. En este artículo, se usan datos simulados, pero también podría llamar a la API de REST de SharePoint para recuperar la lista de listas disponibles en la web actual. Para simular las opciones de carga desde un servicio externo, el método usa un retraso de dos segundos

    private loadLists(): Promise<IDropdownOption[]> {
      return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
        setTimeout(() => {
          resolve([{
            key: 'sharedDocuments',
            text: 'Shared Documents'
          },
            {
              key: 'myDocuments',
              text: 'My Documents'
            }]);
        }, 2000);
      });
    }
    
  4. Agregue un método para controlar el cambio del valor del desplegable. En la clase ListItemsWebPart, agregue un nuevo método denominado onListChange().

    private onListChange(propertyPath: string, newValue: any): void {
      const oldValue: any = get(this.properties, propertyPath);
      // store new value in web part properties
      update(this.properties, propertyPath, (): any => { return newValue; });
      // refresh web part
      this.render();
    }
    

    Después de seleccionar una lista en el desplegable, debe conservarse el valor seleccionado en las propiedades del elemento web y este debería representarse de nuevo para reflejar la propiedad seleccionada.

  5. Represente la propiedad de elemento web de lista mediante el control de panel de propiedades desplegable asincrónico. En la clase ListItemsWebPart, cambie el método getPropertyPaneConfiguration() para usar el control de panel de propiedades desplegable asincrónico para representar la propiedad de elemento web listName.

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    new PropertyPaneAsyncDropdown('listName', {
                      label: strings.ListFieldLabel,
                      loadOptions: this.loadLists.bind(this),
                      onPropertyChange: this.onListChange.bind(this),
                      selectedKey: this.properties.listName
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    
      // ...
    }
    
  6. En este momento, puede seleccionar una lista mediante el control de panel de propiedades desplegable asincrónico recién creado. Para comprobar que el control funciona como se esperaba, abra la consola y ejecute:

    gulp serve
    

    Control de panel de propiedades desplegable asincrónico cargando sus opciones sin bloquear la interfaz de usuario del elemento web

    Seleccionar una de las opciones en el control de panel de propiedades desplegable asincrónico

Implementar desplegables en cascada mediante el control de panel de propiedades desplegable asincrónico

Al compilar elementos web de SharePoint Framework, es posible que necesite implementar una configuración en la que las opciones disponibles dependan de otra opción elegida anteriormente. Un ejemplo común es comenzar dejando que los usuarios elijan una lista y, en esa lista, seleccionar un elemento de lista. La lista de elementos disponibles dependerá de la lista seleccionada. Aquí puede ver cómo implementar un escenario como este mediante el control de panel de propiedades desplegable asincrónico implementado en pasos anteriores.

Agregar propiedad de elemento web del elemento

  1. En el editor de código, abra el archivo src/webparts/listItems/ListItemsWebPart.manifest.json. Agregue una nueva propiedad a la sección properties denominada item similar a la siguiente:

    {
      ...
      "preconfiguredEntries": [{
        ...
        "properties": {
          "description": "List items",
          "listName": "",
          "item": ""
        }
      }]
    }
    
  2. Cambie el código de la interfaz IListItemsWebPartProps en el archivo src/webparts/listItems/ListItemsWebPart.ts a:

    export interface IListItemsWebPartProps {
      description: string;
      listName: string;
      item: string;
    }
    
  3. Actualice el contenido del archivo src/webparts/listItems/components/IListItemsProps.ts para agregar la propiedad item:

    export interface IListItemsProps {
      ...
      listName: string;
      itemName: string;
    }
    
  4. En el archivo src/webparts/listItems/ListItemsWebPart.ts, cambie el código del método render() para incluir la propiedad item:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      public render(): void {
        const element: React.ReactElement<IListItemsProps> = React.createElement(ListItems, {
          listName: this.properties.listName,
          itemName: this.properties.item,
          ...
        });
    
        ReactDom.render(element, this.domElement);
      }
    
      // ...
    }
    
  5. En el archivo src/webparts/listItems/loc/mystrings.d.ts cambie la interfaz IListItemsWebPartStrings para incluir la propiedad ItemFieldLabel:

    declare interface IListItemsWebPartStrings {
      ...
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ListFieldLabel: string;
      ItemFieldLabel: string;
    }
    
  6. En el archivo src/webparts/listItems/loc/en-us.js agregue la definición que falta para la cadena ItemFieldLabel:

    define([], function() {
      return {
        ...
        "PropertyPaneDescription": "Description",
        "BasicGroupName": "Group Name",
        "ListFieldLabel": "List",
        "ItemFieldLabel": "Item"
      }
    });
    

Representar el valor de la propiedad de elemento web del elemento

En el archivo src/webparts/listItems/components/ListItems.tsx, cambie el método render() a:

export default class ListItems extends React.Component<IListItemsProps, {}> {
  public render(): React.ReactElement<IListItemsProps> {
    const {
      description,
      isDarkTheme,
      environmentMessage,
      hasTeamsContext,
      userDisplayName,
      listName,
      itemName
    } = this.props;

    return (
      <section className={`${styles.listItems} ${hasTeamsContext ? styles.teams : ''}`}>
        <div className={styles.welcome}>
          <img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
          <h2>Well done, {escape(userDisplayName)}!</h2>
          <div>{environmentMessage}</div>
          <div>List name: <strong>{escape(listName)}</strong></div>
          <div>Item name: <strong>{escape(itemName)}</strong></div>
        </div>
      </section>
    );
  }
}

Agregar un método para cargar elementos de lista

En el archivo src/webparts/listItems/ListItemsWebPart.ts, en la clase ListItemsWebPart, agregue un nuevo método para cargar los elementos de lista disponibles de la lista seleccionada. Al igual que el método para cargar listas disponibles, usará datos simulados. En función de la lista seleccionada anteriormente, el método loadItems() devuelve elementos de lista ficticia. Cuando no se haya seleccionado ninguna lista, el método resuelve la premisa sin ningún dato.

private loadItems(): Promise<IDropdownOption[]> {
  if (!this.properties.listName) {
    // resolve to empty options since no list has been selected
    return Promise.resolve([]);
  }

  const wp: ListItemsWebPart = this;

  return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
    setTimeout(() => {
      const items = {
        sharedDocuments: [
          {
            key: 'spfx_presentation.pptx',
            text: 'SPFx for the masses'
          },
          {
            key: 'hello-world.spapp',
            text: 'hello-world.spapp'
          }
        ],
        myDocuments: [
          {
            key: 'isaiah_cv.docx',
            text: 'Isaiah CV'
          },
          {
            key: 'isaiah_expenses.xlsx',
            text: 'Isaiah Expenses'
          }
        ]
      };
      resolve(items[wp.properties.listName]);
    }, 2000);
  });
}

Agregar un método para controlar la selección de un elemento

En la clase ListItemsWebPart, agregue un nuevo método denominado onListItemChange(). Después de seleccionar un elemento en el desplegable de elementos, el elemento web debería almacenar el nuevo valor en las propiedades del elemento web y volver a representar el elemento web para reflejar los cambios en la interfaz de usuario.

private onListItemChange(propertyPath: string, newValue: any): void {
  const oldValue: any = get(this.properties, propertyPath);
  // store new value in web part properties
  update(this.properties, propertyPath, (): any => { return newValue; });
  // refresh web part
  this.render();
}

Representar la propiedad de elemento web del elemento en el panel de propiedades

  1. En la clase ListItemsWebPart, agregue una nueva propiedad de clase llamada itemsDropdown:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      private itemsDropDown: PropertyPaneAsyncDropdown;
      // ...
    }
    
  2. Cambie el código del método getPropertyPaneConfiguration() por:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        // reference to item dropdown needed later after selecting a list
        this.itemsDropDown = new PropertyPaneAsyncDropdown('item', {
          label: strings.ItemFieldLabel,
          loadOptions: this.loadItems.bind(this),
          onPropertyChange: this.onListItemChange.bind(this),
          selectedKey: this.properties.item,
          // should be disabled if no list has been selected
          disabled: !this.properties.listName
        });
    
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    new PropertyPaneAsyncDropdown('listName', {
                      label: strings.ListFieldLabel,
                      loadOptions: this.loadLists.bind(this),
                      onPropertyChange: this.onListChange.bind(this),
                      selectedKey: this.properties.listName
                    }),
                    this.itemsDropDown
                  ]
                }
              ]
            }
          ]
        };
      }
      // ...
    }
    

    La lista desplegable de la propiedad item se inicializa de forma similar al desplegable de la propiedad listName. La única diferencia es que, dado que después de seleccionar una lista el desplegable de elementos tiene que actualizarse, se tiene que asignar una instancia del control a la variable de clase.

Cargar elementos de la lista seleccionada

Inicialmente, cuando no se selecciona ninguna lista, el desplegable de elementos se deshabilita y se habilita después de que el usuario seleccione una lista. Después de seleccionar una lista, el desplegable de elementos también carga elementos de la lista de ella.

  1. Para implementar esta lógica, extienda el método onListChange() definido anteriormente a:

    private onListChange(propertyPath: string, newValue: any): void {
      const oldValue: any = get(this.properties, propertyPath);
      // store new value in web part properties
      update(this.properties, propertyPath, (): any => { return newValue; });
      // reset selected item
      this.properties.item = undefined;
      // store new value in web part properties
      update(this.properties, 'item', (): any => { return this.properties.item; });
      // refresh web part
      this.render();
      // reset selected values in item dropdown
      this.itemsDropDown.properties.selectedKey = this.properties.item;
      // allow to load items
      this.itemsDropDown.properties.disabled = false;
      // load items and re-render items dropdown
      this.itemsDropDown.render();
    }
    

    Después de seleccionar una lista, el elemento seleccionado se restablece, se conserva en propiedades de elementos web y se restablece en el desplegable de elementos. La lista desplegable para seleccionar un elemento se habilita y la lista desplegable se actualiza para cargar sus opciones.

  2. Para comprobar que todo funciona como se esperaba, en la consola ejecute:

    gulp serve
    

    Después de agregar el elemento web a la página por primera vez y abrir su panel de propiedades, debería ver los desplegables deshabilitados y cargando sus opciones.

    Desplegables en el panel de propiedades del elemento web cargando sus datos

    Una vez que se hayan cargado las opciones, se habilita el cuadro de lista desplegable. Como aún no ha seleccionado ninguna lista, el desplegable de elementos permanece deshabilitado.

    Desplegable de lista en el panel de propiedades del elemento web habilitado. Desplegable de elementos deshabilitado

    Después de seleccionar una lista en el desplegable de lista, el desplegable de elementos cargará elementos disponibles en ella.

    Desplegable de elementos cargando elementos disponibles después de seleccionar una lista en el desplegable de lista

    Una vez que se hayan cargado los elementos disponibles, se habilita el desplegable de elementos.

    Seleccionar un elemento de lista en el desplegable de elementos en el panel de propiedades del elemento web

    Después de seleccionar un elemento en el desplegable de elementos, el elemento web se actualiza mostrando el elemento seleccionado en su cuerpo.

    Lista seleccionada y elemento representado en el elemento web

  3. Presione CTRL+C en la consola para detener el servidor web local.

Ver también