Usar menús desplegables en cascada en propiedades de elementos web
Al diseñar el panel de propiedades para los elementos web del lado cliente de SharePoint, es posible que tenga una propiedad de elemento web que muestre sus opciones en función del valor seleccionado en otra propiedad. Esta situación suele producirse al implementar controles desplegables en cascada. En este artículo, aprenderá a crear controles desplegables en cascada en el panel de propiedades del elemento web sin desarrollar un control de panel de propiedades personalizado.
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 que se indican en el artículo, asegúrese de configurar el entorno de desarrollo del elemento web del lado cliente de SharePoint.
Crear un proyecto
Empiece por crear una carpeta para el proyecto:
md react-cascadingdropdowns
Vaya a la carpeta del proyecto:
cd react-cascadingdropdowns
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
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):
- ¿Cómo se llama su solución? react-cascadingdropdowns
- ¿Cuál es el tipo de componente del lado cliente que se va a crear?: Elemento web
- ¿Cómo se llama su elemento web?: Enumerar elementos
- ¿Qué plantilla desea usar?: React
Abra la carpeta del proyecto en el editor de código. En los pasos y capturas de pantalla de este artículo, se usa Visual Studio Code, pero puede usar el editor que prefiera.
Defina la propiedad de un elemento web para almacenar la lista seleccionada.
Compilará un elemento web que muestra los elementos de una lista de SharePoint seleccionada. Los usuarios pueden seleccionar una lista en el panel de propiedad del elemento web. Para almacenar la lista seleccionada, cree una propiedad de elemento web denominada listName
.
En el editor de código, abra el archivo src/webparts/listItems/ListItemsWebPartManifest.json. Reemplace la propiedad de
description
predeterminada por una nueva propiedad denominadalistName
.{ ... "preconfiguredEntries": [{ ... "properties": { "listName": "" } }] }
Abra el archivo src/webparts/listItems/ListItemsWebPart.ts y reemplace la interfaz
IListItemsWebPartProps
por lo siguiente:export interface IListItemsWebPartProps { listName: string; }
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 }); ReactDom.render(element, this.domElement); } // ... }
Actualizar
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.ListNameFieldLabel }) ] } ] } ] }; } // ... }
En el archivo src/webparts/listItems/loc/mystrings.d.ts cambie la interfaz
IListItemsStrings
a:declare interface IListItemsStrings { PropertyPaneDescription: string; BasicGroupName: string; ListNameFieldLabel: string; }
En el archivo src/webparts/listItems/loc/en-us.js agregue la definición que falta para la cadena
ListNameFieldLabel
:define([], function() { return { "PropertyPaneDescription": "Description", "BasicGroupName": "Group Name", "ListNameFieldLabel": "List" } });
En el archivo src/webparts/listItems/components/ListItems.tsx, cambie los contenidos del método
render()
a:public render(): JSX.Element { const { 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> ); }
En el archivo src/webparts/listItems/components/IListItemsProps.ts, cambie la interfaz
IListItemsProps
a:export interface IListItemsProps { listName: string; }
Ejecute el comando siguiente para comprobar que el proyecto se está ejecutando:
gulp serve
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.
Rellenar el desplegable con listas de SharePoint
En este punto el usuario especifica qué lista debe usar el elemento web, para lo que escribe manualmente el nombre de la lista. Este proceso es propenso a los errores. Idealmente, los usuarios deberían elegir una de las listas existentes en el sitio de SharePoint actual.
Usar el control desplegable para representar la propiedad listName
En la clase
ListItemsWebPart
, agregue una referencia a la clasePropertyPaneDropdown
en la sección superior del elemento web. Reemplace la cláusula import que carga la clasePropertyPaneTextField
por:import { IPropertyPaneConfiguration, PropertyPaneTextField, PropertyPaneDropdown, IPropertyPaneDropdownOption } from '@microsoft/sp-property-pane';
En la clase
ListItemsWebPart
, agregue una variable llamadalists
para almacenar información sobre todas las listas disponibles en el sitio actual:export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { private lists: IPropertyPaneDropdownOption[]; // ... }
Agregue una nueva variable de clase denominada
listsDropdownDisabled
. Esta variable determina si se habilita el desplegable de listas. Hasta que el elemento web recupere la información sobre las listas disponibles en el sitio actual, el desplegable debería estar inhabilitado.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private listsDropdownDisabled: boolean = true; // ... }
Cambie el método
getPropertyPaneConfiguration()
para usar el control desplegable y representar la propiedadlistName
:export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneDropdown('listName', { label: strings.ListNameFieldLabel, options: this.lists, disabled: this.listsDropdownDisabled }) ] } ] } ] }; } }
Ejecute el comando siguiente para comprobar que está funcionando como se esperaba:
gulp serve
Mostrar las listas disponibles en el desplegable de lista
Anteriormente, asoció el control desplegable de la propiedad listName
a la propiedad de la clase lists
. Dado que todavía no ha cargado ningún valor, el desplegable Lista del panel de propiedades del elemento web permanecerá inhabilitado. En esta sección, ampliará el elemento web para cargar la información sobre las listas disponibles.
En la clase
ListItemsWebPart
, agregue un método para cargar las listas disponibles Usará 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 segundosexport default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private loadLists(): Promise<IPropertyPaneDropdownOption[]> { return new Promise<IPropertyPaneDropdownOption[]>((resolve: (options: IPropertyPaneDropdownOption[]) => void, reject: (error: any) => void) => { setTimeout((): void => { resolve([{ key: 'sharedDocuments', text: 'Shared Documents' }, { key: 'myDocuments', text: 'My Documents' }]); }, 2000); }); } }
Cargue información sobre las listas disponibles en el desplegable de lista. En la clase
ListItemsWebPart
, invalide el métodoonPropertyPaneConfigurationStart()
con el código siguiente:export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... protected onPropertyPaneConfigurationStart(): void { this.listsDropdownDisabled = !this.lists; if (this.lists) { return; } this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'lists'); this.loadLists() .then((listOptions: IPropertyPaneDropdownOption[]): void => { this.lists = listOptions; this.listsDropdownDisabled = false; this.context.propertyPane.refresh(); this.context.statusRenderer.clearLoadingIndicator(this.domElement); this.render(); }); } // ... }
SharePoint Framework llama al método
onPropertyPaneConfigurationStart()
después de que el panel de propiedades del elemento web se haya abierto.- En primer lugar, el método comprueba si se ha cargado la información sobre las listas disponibles en el sitio actual.
- Si se ha cargado la información, se habilita el desplegable de listas.
- Si aún no se ha cargado, se muestra el indicador de carga, que permite al usuario saber si el elemento web está cargando información sobre las listas.
Después de cargar la información sobre las listas disponibles, el método asigna los datos recuperados a la variable de clase
lists
, desde donde puede usarlos el desplegable de listas.Después se habilita el desplegable, lo que permite al usuario seleccionar una lista. Al llamar al método
this.context.propertyPane.refresh()
, el panel de propiedades del elemento web se actualiza para reflejar los últimos cambios en el desplegable de listas.Una vez cargada la información sobre las listas, se quita el indicador de carga mediante una llamada al método
clearLoadingIndicator()
. Puesto que llamar a este método hace que se borre la interfaz de usuario del elemento web, se llama al métodorender()
para que el elemento web vuelva a representarse.Ejecute el comando siguiente para confirmar que todo funciona como se esperaba:
gulp serve
Al agregar un elemento web al lienzo y abrir su panel de propiedades, se mostrará el desplegable de listas, que contiene las listas disponibles para el usuario.
Permitir a los usuarios que elijan un elemento de la lista seleccionada
Al compilar elementos web, a menudo es necesario permitir que los usuarios elijan una opción entre un conjunto de valores determinado por un valor seleccionado previamente (por ejemplo, elegir un país o región según el continente seleccionado, o un elemento de lista a partir de una lista seleccionada). Esta experiencia de usuario se conoce a menudo como menús desplegables en cascada. Gracias a las funciones estándar para elementos web del lado cliente de SharePoint Framework, puede compilar desplegables en cascada en el panel de propiedades del elemento web. Para hacerlo, debe expandir el elemento web ya compilado con la función para elegir un elemento de lista determinado por una lista seleccionada previamente.
Agregar propiedad de elemento web del elemento
En el editor de código, abra el archivo src/webparts/listItems/ListItemsWebPart.manifest.json. Agregue una nueva propiedad a la sección
properties
denominadaitemName
similar a la siguiente:{ // ... "properties": { "listName": "", "itemName": "" } // ... }
Cambie el código en el archivo src/webparts/listItems/IListItemsWebPartProps.ts a:
export interface IListItemsWebPartProps { listName: string; itemName: string; }
Cambie el código en el archivo src/webparts/listItems/components/IListItemsProps.ts a:
export interface IListItemsProps { listName: string; itemName: string; }
En el archivo src/webparts/listItems/ListItemsWebPart.ts, cambie el código del 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, itemName: this.properties.itemName }); ReactDom.render(element, this.domElement); } // ... }
En el archivo src/webparts/listItems/loc/mystrings.d.ts cambie la interfaz
IListItemsStrings
a:declare interface IListItemsStrings { PropertyPaneDescription: string; BasicGroupName: string; ListNameFieldLabel: string; ItemNameFieldLabel: string; }
En el archivo src/webparts/listItems/loc/en-us.js agregue la definición que falta para la cadena
ItemNameFieldLabel
.define([], function() { return { "PropertyPaneDescription": "Description", "BasicGroupName": "Group Name", "ListNameFieldLabel": "List", "ItemNameFieldLabel": "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(): JSX.Element {
const {
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>
);
}
}
Permitir a los usuarios elegir un elemento de una lista
Del mismo modo que los usuarios pueden seleccionar una lista mediante un desplegable, pueden seleccionar un elemento de una lista de elementos disponibles.
En la clase
ListItemsWebPart
, agregue una nueva variable llamadaitems
para almacenar información sobre todos los elementos disponibles en la lista actual.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private items: IPropertyPaneDropdownOption[]; // ... }
Agregue una nueva variable de clase denominada
itemsDropdownDisabled
. Esta variable determina si se habilita el desplegable de elementos. Los usuarios pueden seleccionar un elemento únicamente después de seleccionar una lista.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private itemsDropdownDisabled: boolean = true; // ... }
Cambie el método
getPropertyPaneConfiguration()
para usar el control desplegable y representar la propiedaditemName
.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneDropdown('listName', { label: strings.ListNameFieldLabel, options: this.lists, disabled: this.listsDropdownDisabled }), PropertyPaneDropdown('itemName', { label: strings.ItemNameFieldLabel, options: this.items, disabled: this.itemsDropdownDisabled }) ] } ] } ] }; } }
Ejecute el comando siguiente para comprobar que está funcionando como se esperaba:
gulp serve
Mostrar los elementos disponibles en la lista seleccionada en el desplegable de listas
Anteriormente, definió un control desplegable para representar la propiedad itemName
en el panel de propiedades del elemento web. Después, deberá ampliar el elemento web para cargar la información sobre los elementos disponibles en la lista seleccionada y mostrarlos en el desplegable de elementos.
Agregaue 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.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private loadItems(): Promise<IPropertyPaneDropdownOption[]> { if (!this.properties.listName) { // resolve to empty options since no list has been selected return Promise.resolve(); } const wp: ListItemsWebPart = this; return new Promise<IPropertyPaneDropdownOption[]>((resolve: (options: IPropertyPaneDropdownOption[]) => 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); }); } }
El método
loadItems()
devuelve elementos de lista de simulacro para la lista previamente seleccionada. Cuando no se haya seleccionado ninguna lista, el método resuelve la premisa sin ningún dato.Cargue información sobre los elementos disponibles en el desplegable de elementos. En la clase
ListItemsWebPart
, extienda el métodoonPropertyPaneConfigurationStart()
para cargar los elementos de la lista seleccionada:export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... protected onPropertyPaneConfigurationStart(): void { this.listsDropdownDisabled = !this.lists; this.itemsDropdownDisabled = !this.properties.listName || !this.items; if (this.lists) { return; } this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'options'); this.loadLists() .then((listOptions: IPropertyPaneDropdownOption[]): Promise<IPropertyPaneDropdownOption[]> => { this.lists = listOptions; this.listsDropdownDisabled = false; this.context.propertyPane.refresh(); return this.loadItems(); }) .then((itemOptions: IPropertyPaneDropdownOption[]): void => { this.items = itemOptions; this.itemsDropdownDisabled = !this.properties.listName; this.context.propertyPane.refresh(); this.context.statusRenderer.clearLoadingIndicator(this.domElement); this.render(); }); } // ... }
Al inicializar, el elemento web determina primero si el desplegable de elementos debe habilitarse. Si el usuario ha seleccionado previamente una lista, puede seleccionar un elemento de dicha lista. Si no ha seleccionado ninguna lista, se deshabilita el desplegable de elementos.
Ha ampliado el código previamente definido, que carga la información acerca de las listas disponibles, para cargar la información acerca de los elementos disponibles en la lista seleccionada. A continuación el código asignará la información obtenida a la variable de clase
items
para que la use el desplegable de elementos. Por último, el código borrará el indicador de carga y permitirá al usuario comenzar a trabajar con el elemento web.Ejecute el comando siguiente para confirmar que todo funciona como esperaba:
gulp serve
Según sea necesario, el desplegable de elementos estará inhabilitado inicialmente, por lo que los usuarios deberán seleccionar una lista primero. En este punto, incluso después de seleccionar una lista, el desplegable de elementos permanecerá inhabilitado.
Actualice el panel de propiedades de un elemento web después de seleccionar una lista. Cuando un usuario selecciona una lista en el panel de propiedades, el elemento web debería actualizarse, de modo que se habilite el desplegable de elementos y se muestre la lista de elementos disponibles para la lista seleccionada.
En el archivo ListItemsWebPart de la clase
ListItemsWebPart
, reemplace el métodoonPropertyPaneFieldChanged()
por el código siguiente:export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void { if (propertyPath === 'listName' && newValue) { // push new list value super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue); // get previously selected item const previousItem: string = this.properties.itemName; // reset selected item this.properties.itemName = undefined; // push new item value this.onPropertyPaneFieldChanged('itemName', previousItem, this.properties.itemName); // disable item selector until new items are loaded this.itemsDropdownDisabled = true; // refresh the item selector control by repainting the property pane this.context.propertyPane.refresh(); // communicate loading items this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'items'); this.loadItems() .then((itemOptions: IPropertyPaneDropdownOption[]): void => { // store items this.items = itemOptions; // enable item selector this.itemsDropdownDisabled = false; // clear status indicator this.context.statusRenderer.clearLoadingIndicator(this.domElement); // re-render the web part as clearing the loading indicator removes the web part body this.render(); // refresh the item selector control by repainting the property pane this.context.propertyPane.refresh(); }); } else { super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue); } } // ... }
Cuando el usuario selecciona una lista, el elemento web mantiene el valor seleccionado. Puesto que la lista seleccionada ha cambiado, el elemento web restablece el elemento de lista previamente seleccionado. Con una lista seleccionada, el panel de propiedades del elemento web carga elementos de lista para esa lista en particular. Al cargar elementos, el usuario no puede seleccionar un elemento.
Una vez que los elementos de la lista seleccionada estén cargados, se asignarán a la variable de clase items, lo que permite que se haga referencia a ellos mediante el desplegable de elementos. Ahora que la información sobre los elementos de lista disponibles está presente, el desplegable de elementos se ha habilitado y los usuarios pueden elegir un elemento. Se borra el indicador de carga y, por tanto, también el cuerpo del elemento web. Por este motivo, el elemento web debería volver a representarse. Finalmente, el panel de propiedades del elemento web se actualiza para reflejar los últimos cambios.