Использование каскадных раскрывающихся меню для свойств веб-частей
При разработке области свойств для клиентских веб-частей SharePoint может быть одно свойство веб-части, которое отображает его параметры на основе значения, выбранного в другом свойстве. Такое обычно происходит, если применяются каскадные раскрывающиеся меню. В этой статье описано, как создавать каскадные раскрывающиеся меню в области свойств веб-части, не создавая собственный элемент управления.
Исходный код рабочей веб-части доступен в репозитории sp-dev-fx-webparts/samples/react-custompropertypanecontrols/ на сайте GitHub.
Примечание.
Прежде чем выполнять действия, описанные в этой статье, настройте среду разработки для создания клиентских веб-частей SharePoint.
Создание проекта
Для начала создайте папку проекта:
md react-cascadingdropdowns
Перейдите в папку проекта:
cd react-cascadingdropdowns
В папке проекта запустите генератор Yeoman для SharePoint Framework, чтобы сформировать шаблон проекта на платформе SharePoint Framework:
yo @microsoft/sharepoint
При появлении запроса введите следующие значения (выберите вариант по умолчанию для всех запросов, не перечисленных ниже).
- Как называется решение? react-cascadingdropdowns
- Какой тип клиентского компонента нужно создать? WebPart
- Как называется веб-часть? Элементы списка
- Какой шаблон вы хотите использовать?: React
Откройте папку проекта в редакторе кода. В этой статье в инструкциях и на снимках экрана используется Visual Studio Code, но вы можете использовать любой другой редактор.
Определение свойства веб-части для хранения выбранного списка
Вы создадите веб-часть, в которой отображаются элементы выбранного списка SharePoint. Пользователи смогут выбрать список в области свойств веб-части. Для хранения выбранного списка создайте свойство веб-части с именем listName
.
В редакторе кода откройте файл src/webparts/listItems/ListItemsWebPartManifest.json. Замените свойство по умолчанию
description
на новое свойствоlistName
.{ ... "preconfiguredEntries": [{ ... "properties": { "listName": "" } }] }
Откройте файл src/webparts/listItems/ListItemsWebPart.ts и замените интерфейс
IListItemsWebPartProps
следующим образом.export interface IListItemsWebPartProps { listName: string; }
В файле src/webparts/listItems/ListItemsWebPart.ts замените метод
render()
на следующий: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); } // ... }
Обновите
getPropertyPaneConfiguration()
до: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 }) ] } ] } ] }; } // ... }
В файле src/webparts/listItems/loc/mystrings.d.ts измените интерфейс
IListItemsStrings
на следующий:declare interface IListItemsStrings { PropertyPaneDescription: string; BasicGroupName: string; ListNameFieldLabel: string; }
В файле src/webparts/listItems/loc/en-us.js добавьте отсутствующее определение для строки
ListNameFieldLabel
:define([], function() { return { "PropertyPaneDescription": "Description", "BasicGroupName": "Group Name", "ListNameFieldLabel": "List" } });
В файле src/webparts/listItems/components/ListItems.tsx замените содержимое метода
render()
следующим: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> ); }
В файле src/webparts/listItems/components/IListItemsProps.ts замените интерфейс
IListItemsProps
следующим:export interface IListItemsProps { listName: string; }
Чтобы убедиться, что проект работает, выполните следующую команду:
gulp serve
В веб-браузере добавьте веб-часть List items на полотно и откройте ее свойства. Убедитесь, что значение свойства List отображается в теле веб-части.
Заполнение раскрывающегося меню списками SharePoint, из которых следует выбирать
На этом шаге пользователь указывает имя списка, который должна использовать веб-часть. В этом случае возможна ошибка. В идеале пользователь должен выбрать один из списков на текущем сайте SharePoint.
Отрисовка свойства listName с использованием раскрывающегося меню
В классе
ListItemsWebPart
добавьте ссылку на классPropertyPaneDropdown
в верхнем разделе веб-части. Замените предложение импорта, загружающее классPropertyPaneTextField
, следующим кодом:import { IPropertyPaneConfiguration, PropertyPaneTextField, PropertyPaneDropdown, IPropertyPaneDropdownOption } from '@microsoft/sp-property-pane';
В классе
ListItemsWebPart
добавьте новую переменнуюlists
для хранения сведений обо всех доступных списках на текущем сайте.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { private lists: IPropertyPaneDropdownOption[]; // ... }
Добавьте новую переменную класса под названием
listsDropdownDisabled
. Она определяет, включено ли раскрывающееся меню со списками. Пока веб-часть не получит сведения о доступных списках на текущем сайте, раскрывающееся меню должно быть отключено.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private listsDropdownDisabled: boolean = true; // ... }
Измените метод
getPropertyPaneConfiguration()
, чтобы использовать раскрывающееся меню для отрисовки свойстваlistName
: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 }) ] } ] } ] }; } }
Чтобы проверить правильность работы, выполните указанную ниже команду.
gulp serve
Отображение доступных списков в раскрывающемся меню
Ранее мы сопоставили раскрывающееся меню свойства listName
со свойством класса lists
. Так как мы пока не загрузили в него никаких значений, раскрывающееся меню List в области свойств веб-части остается отключенным. В этом разделе вы расширите возможности веб-части, чтобы она загружала сведения о доступных списках.
В классе
ListItemsWebPart
добавьте метод для загрузки доступных списков. Вы будете применять фиктивные данные, но также можете вызвать REST API для SharePoint, чтобы извлечь список доступных списков из текущего веб-сайта. Для имитации загрузки параметров из внешней службы метод использует двухсекундную задержку.export 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); }); } }
Загрузите сведения о доступных списках в раскрывающееся меню со списками. В классе
ListItemsWebPart
переопределите методonPropertyPaneConfigurationStart()
, используя следующий код.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 вызывает метод
onPropertyPaneConfigurationStart()
после открытия области свойств веб-части.- Сначала метод проверяет, загружена ли информация о списках, доступных на текущем сайте.
- Если она загружена, включается раскрывающееся меню со списками.
- Если же она пока не загружена, появится индикатор загрузки, сообщающий пользователю, что веб-часть загружает информацию о списках.
Когда информация о доступных списках загрузится, метод назначит полученные данные переменной класса
lists
, из которой они попадут в раскрывающееся меню со списками.После этого включается раскрывающееся меню, где пользователь сможет выбрать список. Благодаря вызову метода
this.context.propertyPane.refresh()
обновляется область свойств веб-части, чтобы отобразить последние изменения в раскрывающемся меню со списками.После загрузки сведений индикатор загрузки удаляется вызовом метода
clearLoadingIndicator()
. Так как вызов этого метода очищает интерфейс веб-части, вызывается методrender()
для повторной отрисовки веб-части.Выполните следующую команду, чтобы убедиться, что всё работает правильно:
gulp serve
Когда вы добавляете веб-часть на холст и открываете панель ее свойств, раскрывающееся меню со списками должно заполниться доступными списками, из которых может выбирать пользователь.
Предоставление пользователям возможности выбрать элемент в указанном списке
При создании веб-частей часто требуется предоставить пользователям возможность выбирать из набора значений, которые определяются ранее указанным значением (например, выбрать страну после указания континента или элемент после указания списка). Это можно сделать с помощью каскадных раскрывающихся меню. Вы можете создавать каскадные раскрывающиеся меню в области свойств веб-части, используя стандартные возможности клиентских веб-частей SharePoint Framework. Для этого в веб-часть добавляется возможность выбирать элемент на основе уже выбранного списка.
Добавление свойства веб-части для выбора элементов
В редакторе кода откройте файл src/webparts/listItems/ListItemsWebPart.manifest.json. В разделе
properties
добавьте свойствоitemName
, чтобы оно выглядело следующим образом:{ // ... "properties": { "listName": "", "itemName": "" } // ... }
Замените код в файле src/webparts/listItems/IListItemsWebPartProps.ts следующим:
export interface IListItemsWebPartProps { listName: string; itemName: string; }
Замените код в файле src/webparts/listItems/components/IListItemsProps.ts следующим:
export interface IListItemsProps { listName: string; itemName: string; }
В файле src/webparts/listItems/ListItemsWebPart.ts замените код метода
render()
следующим: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); } // ... }
В файле src/webparts/listItems/loc/mystrings.d.ts измените интерфейс
IListItemsStrings
на следующий:declare interface IListItemsStrings { PropertyPaneDescription: string; BasicGroupName: string; ListNameFieldLabel: string; ItemNameFieldLabel: string; }
В файле src/webparts/listItems/loc/en-us.js добавьте отсутствующее определение для строки
ItemNameFieldLabel
.define([], function() { return { "PropertyPaneDescription": "Description", "BasicGroupName": "Group Name", "ListNameFieldLabel": "List", "ItemNameFieldLabel": "Item" } });
Отрисовка значения свойства веб-части item
В файле src/webparts/listItems/components/ListItems.tsx замените метод render()
на следующий:
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>
);
}
}
Предоставление пользователям возможности выбрать элемент списка
У пользователей есть возможность выбрать элемент списка аналогично тому, как они выбирают список в раскрывающемся меню.
В классе
ListItemsWebPart
добавьте новую переменнуюitems
для хранения информации обо всех доступных элементах в выбранном списке.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private items: IPropertyPaneDropdownOption[]; // ... }
Добавьте новую переменную класса под названием
itemsDropdownDisabled
. Она определяет, следует ли включать раскрывающееся меню. Пользователи могут выбрать элемент только после выбора соответствующего списка.export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> { // ... private itemsDropdownDisabled: boolean = true; // ... }
Измените метод
getPropertyPaneConfiguration()
, чтобы использовать раскрывающееся меню для отрисовки свойстваitemName
.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 }) ] } ] } ] }; } }
Чтобы проверить правильность работы, выполните указанную ниже команду.
gulp serve
Отображение доступных элементов выбранного списка в раскрывающемся меню
Ранее мы определили раскрывающееся меню для отрисовки свойства itemName
в области свойств веб-части. Далее следует добавить в веб-часть возможность загружать сведения о доступных элементах выбранного списка и отображать эти элементы в раскрывающемся меню.
Добавьте метод для загрузки элементов списка. В файле src/webparts/listItems/ListItemsWebPart.ts добавьте метод в классе
ListItemsWebPart
для загрузки доступных элементов из выбранного списка. Как и при загрузке доступных списков, здесь используются фиктивные данные.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); }); } }
Метод
loadItems()
возвращает фиктивные элементы списка для ранее выбранного списка. Если список не выбран, метод разрешает обещание без данных.Загрузите сведения о доступных элементах в раскрывающееся меню. В классе
ListItemsWebPart
измените методonPropertyPaneConfigurationStart()
так, чтобы он загружал элементы выбранного списка: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(); }); } // ... }
При инициализации веб-часть сначала определяет, следует ли включать раскрывающееся меню. Пользователь может выбрать элемент этого списка, если он уже выбрал список. Если список не выбран, раскрывающееся меню будет отключено.
Измените ранее определенный код, загружающий сведения о доступных списках, так, чтобы он загружал и сведения о доступных элементах выбранного списка. Этот код затем назначит полученные сведения переменной класса
items
для использования в раскрывающемся меню. Наконец код очистит индикатор загрузки и позволит пользователю начать работать с веб-частью.Выполните следующую команду, чтобы убедиться, что всё работает правильно:
gulp serve
При необходимости раскрывающееся меню может быть изначально отключено, чтобы пользователи сначала выбирали список. На данном этапе, даже после выбора списка, раскрывающееся меню элементов остается отключенным.
Обновите область свойств веб-части после выбора списка. Когда пользователь выбирает список на панели свойств, страница веб-части должна обновиться так, чтобы включить раскрывающееся меню и отобразить список доступных элементов выбранного списка.
Откройте файл ListItemsWebPart.ts и переопределите в классе
ListItemsWebPart
методonPropertyPaneFieldChanged()
, добавив следующий код: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); } } // ... }
Когда пользователь выберет список, веб-часть сохранит только что выбранное значение. Так как выбранный список изменился, веб-часть сбросит элемент выбранного ранее списка. Когда список будет выбран, на панель свойств загрузятся элементы конкретного списка. При загрузке элементов пользователь не может выбрать элемент.
Когда элементы выбранного списка загрузятся, они будут назначены переменной класса items, где на них сможет ссылаться раскрывающееся меню элементов. После загрузки сведений о доступных элементах списка включится раскрывающееся меню, в котором пользователи смогут выбрать элемент. Индикатор загрузки удаляется, в результате чего очищается интерфейс веб-части, поэтому ее необходимо отрисовать повторно. Область свойств веб-части обновляется, чтобы применить последние изменения.