From 17505b2138e078c046f4b0cce6465ef595d98483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6ller?= Date: Sat, 7 Apr 2018 10:00:31 +0200 Subject: [PATCH 01/35] Pics illustrating ListView.ContextualMenu --- .../docs/assets/ListView.ContextualMenu.PNG | Bin 0 -> 16604 bytes .../assets/ListView.ContextualMenu_clicked.PNG | Bin 0 -> 13466 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/documentation/docs/assets/ListView.ContextualMenu.PNG create mode 100644 docs/documentation/docs/assets/ListView.ContextualMenu_clicked.PNG diff --git a/docs/documentation/docs/assets/ListView.ContextualMenu.PNG b/docs/documentation/docs/assets/ListView.ContextualMenu.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8979db3f2fed7e37cbe1568d942a20456f0d6970 GIT binary patch literal 16604 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 literal 0 HcmV?d00001 diff --git a/docs/documentation/docs/assets/ListView.ContextualMenu_clicked.PNG b/docs/documentation/docs/assets/ListView.ContextualMenu_clicked.PNG new file mode 100644 index 0000000000000000000000000000000000000000..618d1ab5c818e05182fb28c06de48d3b8ca4565d GIT binary patch literal 13466 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%0DA7BGfb24aNdjlBA@8tfQ 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 literal 0 HcmV?d00001 From 371492346424aa8caf35358c245dd4ad268123fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6ller?= Date: Sat, 7 Apr 2018 10:09:57 +0200 Subject: [PATCH 02/35] Create ListView.ContextualMenu.md --- .../docs/controls/ListView.ContextualMenu.md | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 docs/documentation/docs/controls/ListView.ContextualMenu.md diff --git a/docs/documentation/docs/controls/ListView.ContextualMenu.md b/docs/documentation/docs/controls/ListView.ContextualMenu.md new file mode 100644 index 000000000..4839730e8 --- /dev/null +++ b/docs/documentation/docs/controls/ListView.ContextualMenu.md @@ -0,0 +1,110 @@ +# Add a contextual menu +## The ListView column +To add a contextual menu to our ListView we will insert another Viewfield in the webpart code at the position of our choice, for instance after the “Lastname”: +```TypeScript +{ + name: "", + sorting: false, + maxWidth: 40, + render: (rowitem: IListitem) => { + const element:React.ReactElement =React.createElement( + ECB, { + item:rowitem + } + ); + return element; + } +} +``` +We use the render method of IViewField. Inside we create our ECB component and as a property we handover the rowitem which is clicked. +We could also handover functions that shall be executed inside the context menu component but be able to be bound to the parent component, that is the webpart/component which contains the ListView. +## The ContextualMenu component +We now need to create the ECB component which represents our context menu. Therefore we will use a combination of an [IconButton](https://developer.microsoft.com/en-us/fabric#/components/button#Variants) and a [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) from the Office UI Fabric components. +The code for this additional component looks like this: +```TypeScript +import * as React from 'react'; +import { Layer, IconButton, IButtonProps } from 'office-ui-fabric-react'; +import { ContextualMenuItemType } from 'office-ui-fabric-react/lib/ContextualMenu'; +// The following are project specific components +import { IECBProps } from './IECBProps'; +import styles from './ECB.module.scss'; +import { IListitem } from '../../model/IListitem'; + +export class ECB extends React.Component { + + public constructor(props: IECBProps) { + super(props); + + this.state = { + panelOpen: false + }; + } + + public render() { + return
+ console.error('Disabled action should not be clickable.') + } + ] + } } + /> +
+ ; + } + + private handleClick(source:string, event) { + alert(source + ' clicked'); + } +} +``` +One of the things to mention is that in the (totally simplified for demo reasons here) onClick function we hand over one attribute of the clicked item that we have in the components' properties so we can process it in the function. +Another trick is to give an empty iconName for the menuIconProps. This is because once you attach a menuProps Attribute to any Kind of Office UI fabric button a ChevronDown selector on the right side of the button will occur by default. +With the menuIconProps Attribute you can adjust this, that is when specifying an empty Name you can remove it. This is what we want because we only want to have our “MoreVertical” icon which are the three dots in a vertical order. +To place this a bit more centric, we have small CSS manipulation as well: +```SCSS +.ecb { + position: absolute; + top: -3px; + .ecbbutton div { + padding-left: 12px; + } +} +``` +## The result +The result will look like the following: +![ContextualMenu_shown](../assets/ListView.ContextualMenu.png) +And in action it shows which function and item was clicked: +![ContextualMenu_clicked](../assets/ListView.ContextualMenu_clicked.png) From cc4e09651a297d262ada72c33a20478e5b3a423c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6ller?= Date: Sat, 7 Apr 2018 10:18:17 +0200 Subject: [PATCH 03/35] Delete ListView.ContextualMenu.PNG --- .../docs/assets/ListView.ContextualMenu.PNG | Bin 16604 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/documentation/docs/assets/ListView.ContextualMenu.PNG diff --git a/docs/documentation/docs/assets/ListView.ContextualMenu.PNG b/docs/documentation/docs/assets/ListView.ContextualMenu.PNG deleted file mode 100644 index 8979db3f2fed7e37cbe1568d942a20456f0d6970..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16604 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 From 2930931e35b19c22a1f057572ae1cb8e7e5e33d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6ller?= Date: Sat, 7 Apr 2018 10:18:34 +0200 Subject: [PATCH 04/35] Delete ListView.ContextualMenu_clicked.PNG --- .../assets/ListView.ContextualMenu_clicked.PNG | Bin 13466 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/documentation/docs/assets/ListView.ContextualMenu_clicked.PNG diff --git a/docs/documentation/docs/assets/ListView.ContextualMenu_clicked.PNG b/docs/documentation/docs/assets/ListView.ContextualMenu_clicked.PNG deleted file mode 100644 index 618d1ab5c818e05182fb28c06de48d3b8ca4565d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13466 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%0DA7BGfb24aNdjlBA@8tfQ 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 From e26f54f5f9e6200b206dbd3d34aa8757ad8f9266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6ller?= Date: Sat, 7 Apr 2018 10:20:00 +0200 Subject: [PATCH 05/35] Add files via upload --- .../docs/assets/ListView.ContextualMenu.png | Bin 0 -> 13466 bytes .../assets/ListView.ContextualMenu_clicked.png | Bin 0 -> 16604 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/documentation/docs/assets/ListView.ContextualMenu.png create mode 100644 docs/documentation/docs/assets/ListView.ContextualMenu_clicked.png diff --git a/docs/documentation/docs/assets/ListView.ContextualMenu.png b/docs/documentation/docs/assets/ListView.ContextualMenu.png new file mode 100644 index 0000000000000000000000000000000000000000..618d1ab5c818e05182fb28c06de48d3b8ca4565d GIT binary patch literal 13466 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%0DA7BGfb24aNdjlBA@8tfQ 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 literal 0 HcmV?d00001 diff --git a/docs/documentation/docs/assets/ListView.ContextualMenu_clicked.png b/docs/documentation/docs/assets/ListView.ContextualMenu_clicked.png new file mode 100644 index 0000000000000000000000000000000000000000..8979db3f2fed7e37cbe1568d942a20456f0d6970 GIT binary patch literal 16604 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 literal 0 HcmV?d00001 From cf3c76ba812cfc87f8884c74687766104d072f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=B6ller?= Date: Sat, 7 Apr 2018 10:22:22 +0200 Subject: [PATCH 06/35] Update ListView.ContextualMenu.md --- docs/documentation/docs/controls/ListView.ContextualMenu.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/documentation/docs/controls/ListView.ContextualMenu.md b/docs/documentation/docs/controls/ListView.ContextualMenu.md index 4839730e8..28ec3e77a 100644 --- a/docs/documentation/docs/controls/ListView.ContextualMenu.md +++ b/docs/documentation/docs/controls/ListView.ContextualMenu.md @@ -43,7 +43,8 @@ export class ECB extends React.Component { public render() { return
Date: Sat, 7 Apr 2018 10:30:04 +0200 Subject: [PATCH 07/35] Update ListView.md --- docs/documentation/docs/controls/ListView.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/documentation/docs/controls/ListView.md b/docs/documentation/docs/controls/ListView.md index 02c73e406..3dac85910 100644 --- a/docs/documentation/docs/controls/ListView.md +++ b/docs/documentation/docs/controls/ListView.md @@ -54,6 +54,10 @@ const groupByFields: IGrouping[] = [ ]; ``` +## Extend with a ContextualMenu + +To extend the ListView control with a [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) refer to [ListView.ContextualMenu](./ListView.ContextualMenu.md) + ## Implementation The ListView control can be configured with the following properties: From b2417f2f56342bd301c073166407151010abd1ec Mon Sep 17 00:00:00 2001 From: Anthony Conrad Date: Sun, 8 Apr 2018 17:32:34 -0700 Subject: [PATCH 08/35] Added the SPList entities --- src/common/spEntities/ISPList.ts | 8 ++++++++ src/common/spEntities/index.ts | 1 + 2 files changed, 9 insertions(+) create mode 100644 src/common/spEntities/ISPList.ts create mode 100644 src/common/spEntities/index.ts diff --git a/src/common/spEntities/ISPList.ts b/src/common/spEntities/ISPList.ts new file mode 100644 index 000000000..908b836fb --- /dev/null +++ b/src/common/spEntities/ISPList.ts @@ -0,0 +1,8 @@ +export interface ISPList { + Id: string; + Title: string; + BaseTemplate: string; +} +export interface ISPLists { + value: ISPList[]; +} \ No newline at end of file diff --git a/src/common/spEntities/index.ts b/src/common/spEntities/index.ts new file mode 100644 index 000000000..4c36d4965 --- /dev/null +++ b/src/common/spEntities/index.ts @@ -0,0 +1 @@ +export * from './ISPList'; \ No newline at end of file From 7e19fb4d4febd342fbef1e9e7a1b07227a2e8994 Mon Sep 17 00:00:00 2001 From: Anthony Conrad Date: Sun, 8 Apr 2018 17:34:47 -0700 Subject: [PATCH 09/35] Added SharePoint services --- src/services/ISPService.ts | 21 +++++++++++++++++ src/services/SPService.ts | 32 +++++++++++++++++++++++++ src/services/SPServiceFactory.ts | 14 +++++++++++ src/services/SPServiceMock.ts | 40 ++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 src/services/ISPService.ts create mode 100644 src/services/SPService.ts create mode 100644 src/services/SPServiceFactory.ts create mode 100644 src/services/SPServiceMock.ts diff --git a/src/services/ISPService.ts b/src/services/ISPService.ts new file mode 100644 index 000000000..92552c4b5 --- /dev/null +++ b/src/services/ISPService.ts @@ -0,0 +1,21 @@ +import { ISPLists } from "../common/spEntities"; + +export enum LibsOrderBy { + Id = 1, + Title +} +/** + * Options used to sort and filter + */ +export interface ILibsOptions { + orderBy?: LibsOrderBy; + baseTemplate?: number; + includeHidden?: boolean; +} +export interface ISPService { + /** + * Get the lists from SharePoint + * @param options Options used to order and filter during the API query + */ + getLibs(options?: ILibsOptions): Promise; +} \ No newline at end of file diff --git a/src/services/SPService.ts b/src/services/SPService.ts new file mode 100644 index 000000000..d22c3efa8 --- /dev/null +++ b/src/services/SPService.ts @@ -0,0 +1,32 @@ +import { ISPService, ILibsOptions, LibsOrderBy } from "./ISPService"; +import { ISPLists } from "../common/spEntities"; +import { IWebPartContext } from "@microsoft/sp-webpart-base"; +import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http"; + +export class SPService implements ISPService { + private readonly _context: IWebPartContext; + constructor(context: IWebPartContext) { + this._context = context; + } + public getLibs(options?: ILibsOptions): Promise { + let filtered: boolean; + let queryUrl: string = `${this._context.pageContext.web.absoluteUrl}/_api/web/lists?$select=Title,id,BaseTemplate`; + if (options.orderBy !== null) { + queryUrl += `&$orderby=${options.orderBy === LibsOrderBy.Id ? 'Id': 'Title'}`; + } + if (options.baseTemplate !== null) { + queryUrl += `&$filter=BaseTemplate eq ${options.baseTemplate}`; + filtered = true; + } + if (options.includeHidden === false) { + queryUrl += filtered ? ' and Hidden eq false' : '&$filter=Hidden eq false'; + filtered = true; + } + return this._context.spHttpClient.get(queryUrl, SPHttpClient.configurations.v1) + .then((response: SPHttpClientResponse) => { + return response.json(); + }) as Promise; + } +} + +export default SPService; \ No newline at end of file diff --git a/src/services/SPServiceFactory.ts b/src/services/SPServiceFactory.ts new file mode 100644 index 000000000..f26bfdff0 --- /dev/null +++ b/src/services/SPServiceFactory.ts @@ -0,0 +1,14 @@ +import { IWebPartContext } from "@microsoft/sp-webpart-base"; +import { ISPService } from "./ISPService"; +import { Environment, EnvironmentType } from "@microsoft/sp-core-library"; +import SPServiceMock from "./SPServiceMock"; +import SPService from "./SPService"; + +export class SPServiceFactory { + public static createService(context: IWebPartContext, includeDelay?: boolean, delayTimeout?: number): ISPService { + if (Environment.type === EnvironmentType.Local) { + return new SPServiceMock(includeDelay, delayTimeout); + } + return new SPService(context); + } +} \ No newline at end of file diff --git a/src/services/SPServiceMock.ts b/src/services/SPServiceMock.ts new file mode 100644 index 000000000..039f16398 --- /dev/null +++ b/src/services/SPServiceMock.ts @@ -0,0 +1,40 @@ +import { ISPService, ILibsOptions } from "./ISPService"; +import { ISPLists } from "../common/spEntities"; + +export class SPServiceMock implements ISPService { + private _includeDelay?: boolean; + private _delayTimeout?: number; + + constructor(includeDelay?: boolean, delayTimeout?: number) { + this._includeDelay = includeDelay; + this._delayTimeout = delayTimeout || 500; + } + + /** + * The mock lists to present to the local workbench + */ + private static _lists: ISPLists = { + value: [ + { Id: '8dc80f2e-0e01-43ee-b59e-fbbca2d1f35e', Title: 'Mock List One', BaseTemplate: '109' }, + { Id: '772a30d4-2d62-42da-aa48-c2a37971d693', Title: 'Mock List Two', BaseTemplate: '109' }, + { Id: '16c0d1c6-b467-4823-a37b-c308cf730366', Title: 'Mock List Three', BaseTemplate: '109' } + ] + }; + public getLibs(options?: ILibsOptions): Promise { + return new Promise(async resolve => { + if (this._includeDelay === true) { + await this.sleep(this._delayTimeout); // Simulate network load + } + resolve(SPServiceMock._lists); + }); + } + /** + * Locks the thread for the specified amount of time + * @param ms Milliseconds to wait + */ + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +export default SPServiceMock; \ No newline at end of file From f0fe9d9adcb012e951c1166515f8c1e2650f778b Mon Sep 17 00:00:00 2001 From: Anthony Conrad Date: Sun, 8 Apr 2018 17:37:13 -0700 Subject: [PATCH 10/35] Added listPicker control --- src/controls/listPicker/IListPicker.ts | 67 +++++++++ .../listPicker/ListPicker.module.scss | 7 + src/controls/listPicker/ListPicker.tsx | 142 ++++++++++++++++++ src/controls/listPicker/index.ts | 2 + 4 files changed, 218 insertions(+) create mode 100644 src/controls/listPicker/IListPicker.ts create mode 100644 src/controls/listPicker/ListPicker.module.scss create mode 100644 src/controls/listPicker/ListPicker.tsx create mode 100644 src/controls/listPicker/index.ts diff --git a/src/controls/listPicker/IListPicker.ts b/src/controls/listPicker/IListPicker.ts new file mode 100644 index 000000000..9463f8b1c --- /dev/null +++ b/src/controls/listPicker/IListPicker.ts @@ -0,0 +1,67 @@ +import IWebPartContext from "@microsoft/sp-webpart-base/lib/core/IWebPartContext"; +import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; + +import { LibsOrderBy } from "../../../lib/services/ISPService"; + +export interface IListPickerProps { + /** + * The web part context + */ + context: IWebPartContext; + /** + * If provided, additional class name to provide on the dropdown element. + */ + className?: string; + /** + * Whether or not the control is disabled + */ + disabled?: boolean; + /** + * The SharePoint BaseTemplate to filter the list options by + */ + baseTemplate?: number; + /** + * Whether or not to include hidden lists. Default is true + */ + includeHidden?: boolean; + /** + * How to order the lists retrieved from SharePoint + */ + orderBy?: LibsOrderBy; + /** + * Keys of the selected item(s). If you provide this, you must maintain selection + * state by observing onSelectionChanged events and passing a new value in when changed. + */ + selectedList?: string | string[]; + /** + * Optional mode indicates if multi-choice selections is allowed. Default to false + */ + multiSelect?: boolean; + /** + * The label to use + */ + label?: string; + /** + * Input placeholder text. Displayed until option is selected. + */ + placeHolder?: string; + /** + * Callback issues when the selected option changes + */ + onSelectionChanged?: (newValue: string | string[]) => void; +} + +export interface IListPickerState { + /** + * The options available to the listPicker + */ + options: IDropdownOption[]; + /** + * Whether or not the listPicker options are loading + */ + loading: boolean; + /** + * Keys of the currently selected item(s). + */ + selectedList?: string | string[]; +} \ No newline at end of file diff --git a/src/controls/listPicker/ListPicker.module.scss b/src/controls/listPicker/ListPicker.module.scss new file mode 100644 index 000000000..e9b94f41e --- /dev/null +++ b/src/controls/listPicker/ListPicker.module.scss @@ -0,0 +1,7 @@ +.listPicker { + .spinner { + float: right; + margin-top: 10px; + margin-right: -20px; + } +} \ No newline at end of file diff --git a/src/controls/listPicker/ListPicker.tsx b/src/controls/listPicker/ListPicker.tsx new file mode 100644 index 000000000..db1c4d1b6 --- /dev/null +++ b/src/controls/listPicker/ListPicker.tsx @@ -0,0 +1,142 @@ +import * as React from 'react'; +import { IDropdownOption, IDropdownProps, Dropdown } from 'office-ui-fabric-react/lib/components/Dropdown'; +import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/components/Spinner'; + +import { IListPickerProps, IListPickerState } from './IListPicker'; +import { ISPService } from '../../services/ISPService'; +import { SPServiceFactory } from '../../services/SPServiceFactory'; + +import styles from './ListPicker.module.scss'; + +/** + * Empty list value, to be checked for single list selection + */ +const EMPTY_LIST_KEY = 'NO_LIST_SELECTED'; + +/** + * Renders the controls for the ListPicker component + */ +export class ListPicker extends React.Component { + private _options: IDropdownOption[] = []; + private _selectedList: string | string[]; + + /** + * Constructor method + */ + constructor(props: IListPickerProps) { + super(props); + + console.debug('selectedList', this.props.selectedList); + + this.state = { + options: this._options, + loading: false + }; + + this.onChanged = this.onChanged.bind(this); + } + + /** + * Lifecycle hook when component is mounted + */ + public componentDidMount() { + this.loadLists(); + } + + /** + * Loads the list from SharePoint current web site + */ + private loadLists() { + const { context, baseTemplate, includeHidden, orderBy, multiSelect, selectedList } = this.props; + + // Show the loading indicator and disable the dropdown + this.setState({ loading: true }); + + const service: ISPService = SPServiceFactory.createService(context, true, 5000); + service.getLibs({ + baseTemplate: baseTemplate, + includeHidden: includeHidden, + orderBy: orderBy + }).then((results) => { + // Start mapping the lists to the dropdown option + results.value.map(list => { + this._options.push({ + key: list.Id, + text: list.Title + }); + }); + + if (multiSelect !== true) { + // Add option to unselct list + this._options.unshift({ + key: EMPTY_LIST_KEY, + text: '' + }); + } + + this._selectedList = this.props.selectedList; + + // Hide the loading indicator and set the dropdown options and enable the dropdown + this.setState({ + loading: false, + options: this._options, + selectedList: this._selectedList + }); + }); + } + + /** + * Raises when a list has been selected + * @param option the new selection + * @param index the index of the selection + */ + private onChanged(option: IDropdownOption, index?: number): void { + const { multiSelect, onSelectionChanged } = this.props; + + if (multiSelect === true) { + if (this._selectedList === undefined) { + this._selectedList = new Array(); + } + (this._selectedList as string[]).push(option.key as string); + } else { + this._selectedList = option.key as string; + } + + if (onSelectionChanged) { + onSelectionChanged(this._selectedList); + } + } + + /** + * Renders the ListPicker controls with Office UI Fabric + */ + public render(): JSX.Element { + const { loading, options, selectedList } = this.state; + const { className, disabled, multiSelect, label, placeHolder } = this.props; + + const dropdownOptions: IDropdownProps = { + className: className, + options: options, + disabled: ( loading || disabled ), + label: label, + placeHolder: placeHolder, + onChanged: this.onChanged + }; + + if (multiSelect === true) { + dropdownOptions.multiSelect = true; + dropdownOptions.selectedKeys = selectedList as string[]; + } else { + dropdownOptions.selectedKey = selectedList as string; + } + + return ( +
+ { loading && } + +
+ ); + } +} + +export default ListPicker; \ No newline at end of file diff --git a/src/controls/listPicker/index.ts b/src/controls/listPicker/index.ts new file mode 100644 index 000000000..49c69677d --- /dev/null +++ b/src/controls/listPicker/index.ts @@ -0,0 +1,2 @@ +export * from './IListPicker'; +export * from './ListPicker'; \ No newline at end of file From bf1f0840d6d892c88860e487ef25711f1fa4c86a Mon Sep 17 00:00:00 2001 From: Anthony Conrad Date: Sun, 8 Apr 2018 17:38:10 -0700 Subject: [PATCH 11/35] Added listPicker root control and added listPicker to the index --- src/ListPicker.ts | 1 + src/index.ts | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/ListPicker.ts diff --git a/src/ListPicker.ts b/src/ListPicker.ts new file mode 100644 index 000000000..e19ebaafd --- /dev/null +++ b/src/ListPicker.ts @@ -0,0 +1 @@ +export * from './controls/listPicker/index'; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 285167cdb..6264b5d2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,3 +3,4 @@ export * from './ListView'; export * from './Placeholder'; export * from './SiteBreadcrumb'; export * from './WebPartTitle'; +export * from './ListPicker'; From 76a551d9a1cb93aeabed1e9310640f9fa6f65975 Mon Sep 17 00:00:00 2001 From: Anthony Conrad Date: Sun, 8 Apr 2018 18:19:53 -0700 Subject: [PATCH 12/35] Fixed ISPService reference error in IListPicker --- src/controls/listPicker/IListPicker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controls/listPicker/IListPicker.ts b/src/controls/listPicker/IListPicker.ts index 9463f8b1c..bf2bd32fc 100644 --- a/src/controls/listPicker/IListPicker.ts +++ b/src/controls/listPicker/IListPicker.ts @@ -1,7 +1,7 @@ import IWebPartContext from "@microsoft/sp-webpart-base/lib/core/IWebPartContext"; import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; -import { LibsOrderBy } from "../../../lib/services/ISPService"; +import { LibsOrderBy } from "../../services/ISPService"; export interface IListPickerProps { /** From feaf790d9e9bf8499c19b94b9bfb0ebea16cd251 Mon Sep 17 00:00:00 2001 From: Anthony Conrad Date: Sun, 8 Apr 2018 18:41:22 -0700 Subject: [PATCH 13/35] Removed my spEntities and added the SP List entities to the existing SPEntities file. --- src/common/SPEntities.ts | 16 ++++++++++++++++ src/common/spEntities/ISPList.ts | 8 -------- src/common/spEntities/index.ts | 1 - src/services/ISPService.ts | 2 +- src/services/SPService.ts | 2 +- src/services/SPServiceMock.ts | 2 +- 6 files changed, 19 insertions(+), 12 deletions(-) delete mode 100644 src/common/spEntities/ISPList.ts delete mode 100644 src/common/spEntities/index.ts diff --git a/src/common/SPEntities.ts b/src/common/SPEntities.ts index bbe110c39..003bde1b6 100644 --- a/src/common/SPEntities.ts +++ b/src/common/SPEntities.ts @@ -1,3 +1,19 @@ +/** + * Represents SP List + */ +export interface ISPList { + Id: string; + Title: string; + BaseTemplate: string; +} + +/** + * Replica of the returned value from the REST api + */ +export interface ISPLists { + value: ISPList[]; +} + /** * Represents SP Field */ diff --git a/src/common/spEntities/ISPList.ts b/src/common/spEntities/ISPList.ts deleted file mode 100644 index 908b836fb..000000000 --- a/src/common/spEntities/ISPList.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface ISPList { - Id: string; - Title: string; - BaseTemplate: string; -} -export interface ISPLists { - value: ISPList[]; -} \ No newline at end of file diff --git a/src/common/spEntities/index.ts b/src/common/spEntities/index.ts deleted file mode 100644 index 4c36d4965..000000000 --- a/src/common/spEntities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ISPList'; \ No newline at end of file diff --git a/src/services/ISPService.ts b/src/services/ISPService.ts index 92552c4b5..c62fa8a8e 100644 --- a/src/services/ISPService.ts +++ b/src/services/ISPService.ts @@ -1,4 +1,4 @@ -import { ISPLists } from "../common/spEntities"; +import { ISPLists } from "../common/SPEntities"; export enum LibsOrderBy { Id = 1, diff --git a/src/services/SPService.ts b/src/services/SPService.ts index d22c3efa8..99de2c256 100644 --- a/src/services/SPService.ts +++ b/src/services/SPService.ts @@ -1,5 +1,5 @@ import { ISPService, ILibsOptions, LibsOrderBy } from "./ISPService"; -import { ISPLists } from "../common/spEntities"; +import { ISPLists } from "../common/SPEntities"; import { IWebPartContext } from "@microsoft/sp-webpart-base"; import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http"; diff --git a/src/services/SPServiceMock.ts b/src/services/SPServiceMock.ts index 039f16398..6cf8cc12f 100644 --- a/src/services/SPServiceMock.ts +++ b/src/services/SPServiceMock.ts @@ -1,5 +1,5 @@ import { ISPService, ILibsOptions } from "./ISPService"; -import { ISPLists } from "../common/spEntities"; +import { ISPLists } from "../common/SPEntities"; export class SPServiceMock implements ISPService { private _includeDelay?: boolean; From cf6eded3998bfa9d17af30122f0799614a577f51 Mon Sep 17 00:00:00 2001 From: David Opdendries Date: Sun, 15 Apr 2018 14:20:39 +0200 Subject: [PATCH 14/35] Panel works only with termset --- src/TaxonomyPicker.ts | 1 + src/controls/taxonomyPicker/ErrorMessage.tsx | 27 ++ .../taxonomyPicker/ITaxonomyPicker.ts | 113 +++++++ src/controls/taxonomyPicker/ITermPicker.ts | 152 +++++++++ .../taxonomyPicker/TaxonomyPicker.module.scss | 140 +++++++++ .../taxonomyPicker/TaxonomyPicker.tsx | 288 +++++++++++++++++ src/controls/taxonomyPicker/Term.tsx | 69 +++++ src/controls/taxonomyPicker/TermGroup.tsx | 63 ++++ src/controls/taxonomyPicker/TermPicker.tsx | 144 +++++++++ src/controls/taxonomyPicker/TermSet.tsx | 117 +++++++ src/controls/taxonomyPicker/index.ts | 3 + src/index.ts | 2 +- src/services/ISPTermStorePickerService.ts | 74 +++++ src/services/SPTermStorePickerMockService.ts | 154 ++++++++++ src/services/SPTermStorePickerService.ts | 289 ++++++++++++++++++ .../controlsTest/components/ControlsTest.tsx | 22 ++ 16 files changed, 1657 insertions(+), 1 deletion(-) create mode 100644 src/TaxonomyPicker.ts create mode 100644 src/controls/taxonomyPicker/ErrorMessage.tsx create mode 100644 src/controls/taxonomyPicker/ITaxonomyPicker.ts create mode 100644 src/controls/taxonomyPicker/ITermPicker.ts create mode 100644 src/controls/taxonomyPicker/TaxonomyPicker.module.scss create mode 100644 src/controls/taxonomyPicker/TaxonomyPicker.tsx create mode 100644 src/controls/taxonomyPicker/Term.tsx create mode 100644 src/controls/taxonomyPicker/TermGroup.tsx create mode 100644 src/controls/taxonomyPicker/TermPicker.tsx create mode 100644 src/controls/taxonomyPicker/TermSet.tsx create mode 100644 src/controls/taxonomyPicker/index.ts create mode 100644 src/services/ISPTermStorePickerService.ts create mode 100644 src/services/SPTermStorePickerMockService.ts create mode 100644 src/services/SPTermStorePickerService.ts diff --git a/src/TaxonomyPicker.ts b/src/TaxonomyPicker.ts new file mode 100644 index 000000000..c704d8d8c --- /dev/null +++ b/src/TaxonomyPicker.ts @@ -0,0 +1 @@ +export * from './controls/TaxonomyPicker/index'; diff --git a/src/controls/taxonomyPicker/ErrorMessage.tsx b/src/controls/taxonomyPicker/ErrorMessage.tsx new file mode 100644 index 000000000..a8acea485 --- /dev/null +++ b/src/controls/taxonomyPicker/ErrorMessage.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import styles from './TaxonomyPicker.module.scss'; +import { Icon } from 'office-ui-fabric-react/lib/Icon'; + +export interface IFieldErrorMessageProps { + errorMessage: string; +} + +/** + * Component that shows an error message when something went wront with the property control + */ +export default class FieldErrorMessage extends React.Component { + public render(): JSX.Element { + if (this.props.errorMessage !== 'undefined' && this.props.errorMessage !== null && this.props.errorMessage !== '') { + return ( +
+

+ + {this.props.errorMessage} +

+
+ ); + } else { + return null; + } + } +} diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts new file mode 100644 index 000000000..1c9d69049 --- /dev/null +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -0,0 +1,113 @@ +import { IPickerTerms } from './ITermPicker'; +import { ITermStore, IGroup, ITermSet, ITerm } from '../../services/ISPTermStorePickerService'; +import SPTermStorePickerService from '../../services/SPTermStorePickerService'; +import { IWebPartContext } from '@microsoft/sp-webpart-base'; + +/** + * PropertyFieldTermPickerHost properties interface +// */ +export interface ITaxonomyPickerProps { + /** + * Property field label displayed on top + */ + label: string; + /** + * TermSet Picker Panel title + */ + panelTitle: string; + /** + * Defines if the user can select only one or many term sets. Default value is false. + */ + allowMultipleSelections?: boolean; + /** + * Defines the selected by default term sets. + */ + initialValues?: IPickerTerms; + /** + * WebPart's context + */ + context: IWebPartContext; + /** + * Limit the terms that can be picked by the Term Set name or ID + */ + termsetNameOrID: string; + /** + * Id of a child term in the termset where to be able to selected and search the terms from + */ + ancoreId?: string; + /** + * Whether the property pane field is enabled or not. + */ + disabled?: boolean; + /** + * The method is used to get the validation error message and determine whether the input value is valid or not. + * + * When it returns string: + * - If valid, it returns empty string. + * - If invalid, it returns the error message string and the text field will + * show a red border and show an error message below the text field. + * + * When it returns Promise: + * - The resolved value is display as error message. + * - The rejected, the value is thrown away. + * + */ + onGetErrorMessage?: (value: IPickerTerms) => string | Promise; + /** + * Custom Field will start to validate after users stop typing for `deferredValidationTime` milliseconds. + * Default value is 200. + */ + deferredValidationTime?: number; + + onChange?: (newValue?: IPickerTerms) => void; +} + +/** + * PropertyFieldTermPickerHost state interface + */ +export interface ITaxonomyPickerState { + + termSetAndTerms? : ITermSet; + errorMessage?: string; + openPanel?: boolean; + loaded?: boolean; + activeNodes?: IPickerTerms; +} + +export interface ITermChanges { + changedCallback: (term: ITerm, checked: boolean) => void; + activeNodes?: IPickerTerms; +} + +export interface ITermGroupProps extends ITermChanges { + group: IGroup; + termstore: string; + termsService: SPTermStorePickerService; + multiSelection: boolean; +} + +export interface ITermGroupState { + expanded: boolean; +} + +export interface ITermSetProps extends ITermChanges { + termset: ITermSet; + autoExpand: () => void; + multiSelection: boolean; +} + +export interface ITermSetState { + + loaded?: boolean; + expanded?: boolean; +} + +export interface ITermProps extends ITermChanges { + termset: string; + term: ITerm; + multiSelection: boolean; +} + +export interface ITermState { + selected?: boolean; +} diff --git a/src/controls/taxonomyPicker/ITermPicker.ts b/src/controls/taxonomyPicker/ITermPicker.ts new file mode 100644 index 000000000..2e0cd3147 --- /dev/null +++ b/src/controls/taxonomyPicker/ITermPicker.ts @@ -0,0 +1,152 @@ +import { IWebPartContext } from '@microsoft/sp-webpart-base'; + + + +/** + * Selected terms + */ +export interface IPickerTerm { + name: string; + key: string; + path: string; + termSet: string; + termSetName? : string; +} + +export interface IPickerTerms extends Array { } + +/** + * Generic Term Object (abstract interface) + */ +export interface ISPTermObject { + Name: string; + Guid: string; + Identity: string; + leaf: boolean; + children?: ISPTermObject[]; + collapsed?: boolean; + type: string; +} + +/** + * Defines a SharePoint Term Store + */ +export interface ISPTermStore extends ISPTermObject { + IsOnline: boolean; + WorkingLanguage: string; + DefaultLanguage: string; + Languages: string[]; +} + +/** + * Defines an array of Term Stores + */ +export interface ISPTermStores extends Array { +} + +/** + * Defines a Term Store Group of term sets + */ +export interface ISPTermGroup extends ISPTermObject { + IsSiteCollectionGroup: boolean; + IsSystemGroup: boolean; + CreatedDate: string; + LastModifiedDate: string; +} + +/** + * Array of Term Groups + */ +export interface ISPTermGroups extends Array { +} + + +/** + * Public properties of the PropertyFieldTermPicker custom field + */ +export interface IPropertyFieldTermPickerProps { + /** + * Property field label displayed on top + */ + label: string; + /** + * TermSet Picker Panel title + */ + panelTitle: string; + /** + * Defines if the user can select only one or many term sets. Default value is false. + */ + allowMultipleSelections?: boolean; + /** + * Defines the selected by default term sets. + */ + initialValues?: IPickerTerms; + /** + * Indicator to define if the system Groups are exclude. Default is false. + */ + excludeSystemGroup?: boolean; + /** + * WebPart's context + */ + context: IWebPartContext; + /** + * Limit the term sets that can be used by the group name or ID + */ + limitByGroupNameOrID?: string; + /** + * Limit the terms that can be picked by the Term Set name or ID + */ + limitByTermsetNameOrID?: string; + /** + * Defines a onPropertyChange function to raise when the selected value changed. + * Normally this function must be always defined with the 'this.onPropertyChange' + * method of the web part object. + */ + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + /** + * Parent Web Part properties + */ + properties: any; + /** + * An UNIQUE key indicates the identity of this control + */ + key: string; + /** + * Whether the property pane field is enabled or not. + */ + disabled?: boolean; + /** + * The method is used to get the validation error message and determine whether the input value is valid or not. + * + * When it returns string: + * - If valid, it returns empty string. + * - If invalid, it returns the error message string and the text field will + * show a red border and show an error message below the text field. + * + * When it returns Promise: + * - The resolved value is display as error message. + * - The rejected, the value is thrown away. + * + */ + onGetErrorMessage?: (value: IPickerTerms) => string | Promise; + /** + * Custom Field will start to validate after users stop typing for `deferredValidationTime` milliseconds. + * Default value is 200. + */ + deferredValidationTime?: number; + /** + * Specifies if you want to show or hide the term store name from the panel + */ + hideTermStoreName?: boolean; +} + +/** + * Private properties of the PropertyFieldTermPicker custom field. + * We separate public & private properties to include onRender & onDispose method waited + * by the PropertyFieldCustom, witout asking to the developer to add it when he's using + * the PropertyFieldTermPicker. + */ +// export interface IPropertyFieldTermPickerPropsInternal extends IPropertyFieldTermPickerProps { +// // // onRender(elem: HTMLElement): void; +// // // onDispose(elem: HTMLElement): void; +// } diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.module.scss b/src/controls/taxonomyPicker/TaxonomyPicker.module.scss new file mode 100644 index 000000000..c67d90b20 --- /dev/null +++ b/src/controls/taxonomyPicker/TaxonomyPicker.module.scss @@ -0,0 +1,140 @@ +.listItem { + height: 36px; + line-height: 36px; + cursor: pointer; + + >div { + display: inline-block; + margin-right: 10px; + } + + img { + margin-right: 5px; + vertical-align: middle; + } +} + +.termFieldTable { + border-spacing: 0; + width: 100%; + + .termFieldRow { + vertical-align: initial; + } + + input[type="text"] { + cursor: pointer; + opacity: 0.8; + width: 100%; + } +} + +.termset { + cursor: pointer; + margin-left: 15px; +} + +.term { + padding-left: 20px; + + .termEnabled, + .termDisabled { + background-repeat: no-repeat; + background-position: 30px 3px; + } + + .termEnabled { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACzSURBVDhPY2AYNKCoqIgTiOcD8X8S8F6wB4Aa1IH4akNDw+mPHz++/E8EuHTp0jmQRSDNCcXFxa/XrVt3gAh9KEpgBvx/9OjRLVI1g9TDDYBp3rlz5//Kysr/IJoYgGEASPPatWsbQDQxAMOAbdu2gZ0FookBcAOePHlyhxgN6GqQY+Hdhg0bDpJqCNgAaDrQAnJuNDY2nvr06dMbYgw6e/bsabgBUEN4yEiJ2wdNViLfIQC3sTh2vtJcswAAAABJRU5ErkJggg=='); // /_layouts/15/Images/EMMTerm.png + } + + .termDisabled { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFMSURBVDhPzZPNSsNAFIULQqEQEPoSQqAg7gqC0HdwXxAElyYgBBGSZ+gDdOUyIauULATdKrgIKCKuQjbiDySCkNV4TrkzpJW2cefAxwwzc86de2em0/k3zXGcHpgC9Qeu5glAsAMefN+/K8vyVbVoWZbdMxDFY9d136Ioum6hW9iiDVSe588rxDXmJ+AAdAWOOVcbAy1O01R5nqfYoxVglyk+Hu7Z4FiwOcc1GBRMwQSnOAxDHz0jDyCwwCVQS3DO0gU0BkmSzG8A/UQiz7DxC5yLGQ1PwDeYGYOiKF6WarCPDUOJeor+A4z0m8P4SNaG+hY+4zi+aZh0scEBNeB41DTBuCcGjj6FjaM/BUFwW1XVO6vdMNiSdIzJLwN5TJZ+iSLQKYwbR9cmZyaFdX+JhZIiMue+cLFQxA0G22uusd/6I8OEb4LXRwZN4Q+3Ys8Mb9+nRgAAAABJRU5ErkJggg=='); // /_layouts/15/Images/EMMTermDeprecated.png + } + + label>span { + padding-left: 25px; + } +} + +.actions { + button:first-child { + margin-right: 15px; + } +} + +.termBasePicker +{ + background-color: #fff; +} + .termSuggestion + { + min-height: 40px; + width: 100%; + text-align: left; + cursor: pointer; + + + .termSuggestionSubTitle + { + font-size: 12px; + color: #666666; + } + + } + + .pickedTermRoot + { + position: relative; + outline: transparent; + box-sizing: content-box; + flex-shrink: 1; + background: #f4f4f4; + margin: 2px; + height: 26px; + line-height: 26px; + cursor: default; + display: flex; + flex-wrap: nowrap; + max-width: 300px; + + .pickedTermText + { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 30px; + margin: 0 8px; + } + .pickedTermCloseIcon + { + cursor: pointer; + color: #666666; + font-size: 12px; + display: inline-block; + text-align: center; + vertical-align: top; + width: 30px; + height: 100%; + -ms-flex-negative: 0; + flex-shrink: 0; + } + } + + .errorMessage { + font-size: 12px; + font-weight: 400; + color: #a80000; + margin: 0; + padding-top: 5px; + display: flex; + align-items: center; + } + + .errorIcon { + font-size: 14px; + margin-right: 5px; + } + + + + diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx new file mode 100644 index 000000000..a7430502f --- /dev/null +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -0,0 +1,288 @@ +import * as React from 'react'; +import { IWebPartContext } from '@microsoft/sp-webpart-base'; +import { Async } from 'office-ui-fabric-react/lib/Utilities'; +import { PrimaryButton, DefaultButton, IconButton, IButtonProps } from 'office-ui-fabric-react/lib/Button'; +import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; +import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; +//import { IPropertyFieldTermPickerPropsInternal } from './ITermPicker'; +import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http'; +import { Label } from 'office-ui-fabric-react/lib/Label'; +import TermPicker from './TermPicker'; +import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers'; + +import { IPickerTerms, IPickerTerm } from './ITermPicker'; +import { ITaxonomyPickerProps, ITaxonomyPickerState, ITermGroupProps, ITermGroupState, ITermSetProps, ITermSetState, ITermProps, ITermState } from './ITaxonomyPicker'; +import SPTermStorePickerService from './../../services/SPTermStorePickerService'; +import { ITermSet, IGroup, ITerm } from './../../services/ISPTermStorePickerService'; +import styles from './TaxonomyPicker.module.scss'; +import { sortBy, uniqBy } from '@microsoft/sp-lodash-subset'; +import TermSet from './TermSet'; +import FieldErrorMessage from './ErrorMessage'; +import * as appInsights from '../../common/appInsights'; + + +/** + * Image URLs / Base64 + */ +export const COLLAPSED_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAUCAYAAABSx2cSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjEwcrIlkgAAAIJJREFUOE/NkjEKwCAMRdu7ewZXJ/EqHkJwE9TBCwR+a6FLUQsRwYBTeD8/35wADnZVmPvY4OOYO3UNbK1FKeUWH+fRtK21hjEG3vuhQBdOKUEpBedcV6ALExFijJBSIufcFBjCVSCEACEEqpNvBmsmT+3MTnvqn/+O4+1vdtv7274APmNjtuXVz6sAAAAASUVORK5CYII='; // /_layouts/15/images/MDNCollapsed.png +export const EXPANDED_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAUCAYAAABSx2cSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjEwcrIlkgAAAFtJREFUOE9j/P//PwPZAKSZXEy2RrCLybV1CGjetWvX/46ODqBLUQOXoJ9BGtXU1MCYJM0wjZGRkaRpRtZIkmZ0jSRpBgUOzJ8wmqwAw5eICIb2qGYSkyfNAgwAasU+UQcFvD8AAAAASUVORK5CYII='; // /_layouts/15/images/MDNExpanded.png +export const GROUP_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAC9SURBVDhPY2CgNXh1qEkdiJ8D8X90TNBuJM0V6IpBhoHFgIxebKYTIwYzAMNpxGhGdsFwNoBgNEFjAWsYgOSKiorMgPgbEP/Hgj8AxXpB0Yg1gQAldYuLix8/efLkzn8s4O7du9eAan7iM+DV/v37z546der/jx8/sJkBdhVOA5qbm08ePnwYrOjQoUOkGwDU+AFowLmjR4/idwGukAYaYAkMgxfPnj27h816kDg4DPABoAI/IP6DIxZA4l0AOd9H3QXl5+cAAAAASUVORK5CYII='; // /_layouts/15/Images/EMMGroup.png +export const TERMSET_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACaSURBVDhPrZLRCcAgDERdpZMIjuQA7uWH4CqdxMY0EQtNjKWB0A/77sxF55SKMTalk8a61lqCFqsLiwKac84ZRUUBi7MoYHVmAfjfjzE6vJqZQfie0AcwBQVW8ATi7AR7zGGGNSE6Q2cyLSPIjRswjO7qKhcPDN2hK46w05wZMcEUIG+HrzzcrRsQBIJ5hS8C9fGAPmRwu/9RFxW6L8CM4Ry8AAAAAElFTkSuQmCC'; // /_layouts/15/Images/EMMTermSet.png + + +/** + * Renders the controls for PropertyFieldTermPicker component + */ +export class TaxonomyPicker extends React.Component { + private async: Async; + private delayedValidate: (value: IPickerTerms) => void; + private termsService: SPTermStorePickerService; + private previousValues: IPickerTerms = []; + private cancel: boolean = true; + + /** + * Constructor method + */ + constructor(props: ITaxonomyPickerProps) { + super(props); + + appInsights.track('ReactTaxonomyPicker'); + + + this.state = { + activeNodes: typeof this.props.initialValues !== 'undefined' ? this.props.initialValues : [], + termSetAndTerms: null, + loaded: false, + openPanel: false, + errorMessage: '' + }; + + this.onOpenPanel = this.onOpenPanel.bind(this); + this.onClosePanel = this.onClosePanel.bind(this); + this.onSave = this.onSave.bind(this); + this.termsChanged = this.termsChanged.bind(this); + this.async = new Async(this); + this.termsFromPickerChanged = this.termsFromPickerChanged.bind(this); + } + + /** + * Loads the list from SharePoint current web site + */ + private loadTermStores(): void { + this.termsService = new SPTermStorePickerService(this.props, this.props.context); + this.termsService.getAllTerms(this.props.termsetNameOrID).then((response: ITermSet) => { + console.log(response); + // Check if a response was retrieved + if (response !== null) { + console.log(response); + this.setState({ + termSetAndTerms: response, + loaded: true + }); + } else { + this.setState({ + termSetAndTerms: null, + loaded: true + }); + } + }); + } + + /** + * Open the right Panel + */ + private onOpenPanel(): void { + if (this.props.disabled === true) { + return; + } + + // Store the current code value + this.previousValues = this.state.activeNodes; + this.cancel = true; + + this.loadTermStores(); + + this.setState({ + openPanel: true, + loaded: false + }); + } + + /** + * Close the panel + */ + private onClosePanel(): void { + + this.setState(() => { + const newState: ITaxonomyPickerState = { + openPanel: false, + loaded: false + }; + + // Check if the property has to be reset + if (this.cancel) { + newState.activeNodes = this.previousValues; + } + + return newState; + }); + } + + /** + * On save click action + */ + private onSave(): void { + this.cancel = false; + this.onClosePanel(); + } + + /** + * Clicks on a node + * @param node + */ + private termsChanged(term: ITerm, checked: boolean): void { + + let activeNodes = this.state.activeNodes; + if (typeof term === 'undefined' || term === null) { + return; + } + + // Term item to add to the active nodes array + const termItem = { + name: term.Name, + key: term.Id, + path: term.PathOfTerm, + termSet: term.TermSet.Id + }; + + // Check if the term is checked or unchecked + if (checked) { + // Check if it is allowed to select multiple terms + if (this.props.allowMultipleSelections) { + // Add the checked term + activeNodes.push(termItem); + // Filter out the duplicate terms + activeNodes = uniqBy(activeNodes, 'key'); + } else { + // Only store the current selected item + activeNodes = [termItem]; + } + } else { + // Remove the term from the list of active nodes + activeNodes = activeNodes.filter(item => item.key !== term.Id); + } + // Sort all active nodes + activeNodes = sortBy(activeNodes, 'path'); + // Update the current state + this.setState({ + activeNodes: activeNodes + }); + } + + /** + * Fires When Items Changed in TermPicker + * @param node + */ + private termsFromPickerChanged(terms: IPickerTerms) { + this.props.onChange(terms); + this.setState({ + activeNodes: terms + }); + } + + + /** + * Gets the given node position in the active nodes collection + * @param node + */ + private getSelectedNodePosition(node: IPickerTerm): number { + for (let i = 0; i < this.state.activeNodes.length; i++) { + if (node.key === this.state.activeNodes[i].key) { + return i; + } + } + return -1; + } + + /** + * Called when the component will unmount + */ + public componentWillUnmount() { + if (typeof this.async !== 'undefined') { + this.async.dispose(); + } + } + + /** + * Renders the SPListpicker controls with Office UI Fabric + */ + public render(): JSX.Element { + + return ( +
+ {this.props.label && } + + + + + + + +
+ + + +
+ + + + { + return ( +
+ + + +
+ ); + }}> + + { + /* Show spinner in the panel while retrieving terms */ + this.state.loaded === false ? : '' + } + + + + {this.state.loaded === true && + + + +
+ +

{this.state.termSetAndTerms.Name}

+ {/* */} + +
+ + + } + +
+
+ ); + } +} diff --git a/src/controls/taxonomyPicker/Term.tsx b/src/controls/taxonomyPicker/Term.tsx new file mode 100644 index 000000000..5fe964179 --- /dev/null +++ b/src/controls/taxonomyPicker/Term.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox'; +import { ITermProps, ITermState } from './ITaxonomyPicker'; + +import styles from './TaxonomyPicker.module.scss'; + + +/** + * Term component + * Renders a selectable term + */ +export default class Term extends React.Component { + constructor(props: ITermProps) { + super(props); + + // Check if current term is selected + let active = this.props.activeNodes.filter(item => item.key === this.props.term.Id); + + this.state = { + selected: active.length > 0 + }; + + this._handleChange = this._handleChange.bind(this); + } + + /** + * Handle the checkbox change trigger + */ + private _handleChange(ev: React.FormEvent, isChecked: boolean) { + console.log("handling change"); + this.setState({ + selected: isChecked + }); + this.props.changedCallback(this.props.term, isChecked); + } + + /** + * Lifecycle event hook when component retrieves new properties + * @param nextProps + * @param nextContext + */ + public componentWillReceiveProps?(nextProps: ITermProps, nextContext: any): void { + // If multi-selection is turned off, only a single term can be selected + if (!this.props.multiSelection) { + let active = nextProps.activeNodes.filter(item => item.key === this.props.term.Id); + this.state = { + selected: active.length > 0 + }; + } + } + + + public render(): JSX.Element { + const styleProps: React.CSSProperties = { + marginLeft: `${(this.props.term.PathDepth * 30)}px` + }; + + return ( +
+ +
+ ); + } +} diff --git a/src/controls/taxonomyPicker/TermGroup.tsx b/src/controls/taxonomyPicker/TermGroup.tsx new file mode 100644 index 000000000..186acc33c --- /dev/null +++ b/src/controls/taxonomyPicker/TermGroup.tsx @@ -0,0 +1,63 @@ +// import * as React from 'react'; +// import { ITermGroupProps, ITermGroupState } from './ITaxonomyPicker'; +// import { GROUP_IMG, EXPANDED_IMG, COLLAPSED_IMG } from './TaxonomyPicker'; +// import TermSet from './TermSet'; + +// import styles from './TaxonomyPicker.module.scss'; + +// /** +// * Term group component +// */ +// export default class TermGroup extends React.Component { +// constructor(props: ITermGroupProps) { +// super(props); + +// this.state = { +// expanded: false +// }; + +// this._handleClick = this._handleClick.bind(this); +// this._autoExpand = this._autoExpand.bind(this); +// } + +// /** +// * Handle the click event: collapse or expand +// */ +// private _handleClick() { +// this.setState({ +// expanded: !this.state.expanded +// }); +// } + +// /** +// * Function to auto expand the termset +// */ +// private _autoExpand() { +// this.setState({ +// expanded: true +// }); +// } + +// public render(): JSX.Element { +// // Specify the inline styling to show or hide the termsets +// const styleProps: React.CSSProperties = { +// display: this.state.expanded ? 'block' : 'none' +// }; + +// return ( +//
+//
+// Expand This Node +// Menu for Group {this.props.group.Name} +//
+//
+// { +// this.props.group.TermSets._Child_Items_.map(termset => { +// return ; +// }) +// } +//
+//
+// ); +// } +// } diff --git a/src/controls/taxonomyPicker/TermPicker.tsx b/src/controls/taxonomyPicker/TermPicker.tsx new file mode 100644 index 000000000..4331f0fec --- /dev/null +++ b/src/controls/taxonomyPicker/TermPicker.tsx @@ -0,0 +1,144 @@ +import * as React from 'react'; +import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers'; +import { IPickerTerm, IPickerTerms } from './ITermPicker'; +import SPTermStorePickerService from './../../services/SPTermStorePickerService'; +import styles from './TaxonomyPicker.Module.scss'; +import { ITaxonomyPickerProps } from './ITaxonomyPicker'; +import { IWebPartContext } from '@microsoft/sp-webpart-base'; + +export class TermBasePicker extends BasePicker> +{ + +} + +export interface ITermPickerState { + terms: IPickerTerms; +} + +export interface ITermPickerProps { + termPickerHostProps: ITaxonomyPickerProps; + context: IWebPartContext; + disabled: boolean; + value: IPickerTerms; + onChanged: (items: IPickerTerm[]) => void; + allowMultipleSelections : boolean; +} + +export default class TermPicker extends React.Component { + + /** + * Constructor method + */ + constructor(props: any) { + super(props); + this.onRenderItem = this.onRenderItem.bind(this); + this.onRenderSuggestionsItem = this.onRenderSuggestionsItem.bind(this); + this.onFilterChanged = this.onFilterChanged.bind(this); + this.onGetTextFromItem = this.onGetTextFromItem.bind(this); + + this.state = { + terms: this.props.value + }; + + } + + /** + * componentWillReceiveProps method + */ + public componentWillReceiveProps(nextProps: ITermPickerProps) { + // check to see if props is different to avoid re-rendering + let newKeys = nextProps.value.map(a => a.key); + let currentKeys = this.state.terms.map(a => a.key); + if (newKeys.sort().join(',') !== currentKeys.sort().join(',')) { + this.setState({ terms: nextProps.value }); + } + } + + /** + * Renders the item in the picker + */ + protected onRenderItem(term: IPickerItemProps) { + return (
+ {term.item.name} + {!term.disabled && + + + + } +
); + } + + /** + * Renders the suggestions in the picker + */ + protected onRenderSuggestionsItem(term: IPickerTerm, props) { + let termParent = term.termSetName; + let termTitle = `${term.name} [${term.termSetName}]`; + if (term.path.indexOf(";") !== -1) { + let splitPath = term.path.split(";"); + termParent = splitPath[splitPath.length - 2]; + splitPath.pop(); + termTitle = `${term.name} [${term.termSetName}:${splitPath.join(':')}]`; + } + return (
+
{term.name}
+
in {termParent}
+
); + } + + /** + * When Filter Changes a new search for suggestions + */ + private onFilterChanged(filterText: string, tagList: IPickerTerm[]): Promise { + + if (filterText !== "") { + let termsService = new SPTermStorePickerService(this.props.termPickerHostProps, this.props.context); + let terms = termsService.searchTermsByName(filterText); + return terms; + } + + } + + + /** + * gets the text from an item + */ + private onGetTextFromItem(item: any): any { + return item.name; + } + + /** + * Render method + */ + public render(): JSX.Element { + + // set to 1 if mutiple selections is false + let itemLimit; + if (!this.props.allowMultipleSelections) + { + itemLimit = 1; + } + + return ( +
+ +
+ ); + + } +} diff --git a/src/controls/taxonomyPicker/TermSet.tsx b/src/controls/taxonomyPicker/TermSet.tsx new file mode 100644 index 000000000..2a3c970c4 --- /dev/null +++ b/src/controls/taxonomyPicker/TermSet.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; +import { ITermSetProps, ITermSetState } from './ITaxonomyPicker'; +import { ITerm, ITermSet } from '../../services/ISPTermStorePickerService'; +import { EXPANDED_IMG, COLLAPSED_IMG, TERMSET_IMG } from './TaxonomyPicker'; +import Term from './Term'; + +import styles from './TaxonomyPicker.module.scss'; + +/** + * Term set component + */ +export default class TermSet extends React.Component { + constructor(props: ITermSetProps) { + super(props); + + this.state = { + loaded: true, + expanded: true + }; + + // Check if the termset has to be automatically opened + const selectedTermsInSet = this.props.activeNodes.filter(node => node.termSet === this.props.termset.Id); + if (selectedTermsInSet.length > 0) { + this._autoLoadTerms(); + } + + this._handleClick = this._handleClick.bind(this); + this._loadTerms = this._loadTerms.bind(this); + } + + /** + * Autoload the terms of the term set + */ + private _autoLoadTerms() { + // this.props.autoExpand(); + this._loadTerms(true); + } + + /** + * Handle the click event: collapse or expand + */ + private _handleClick() { + this.setState({ + expanded: !this.state.expanded + }); + + if (!this.state.expanded) { + this._loadTerms(); + } + } + + + /** + * Load the terms for the current term set + */ + private async _loadTerms(autoExpand?: boolean) { + // Check if there are already terms loaded + if (!this.state.loaded) { + // Receive all the terms for the current term set + // const termSet: ITermSet = await this.props.termsService.getAllTerms(this.props.termset._ObjectIdentity_); + // if (termSet.Terms !== null) { + // this.setState({ + // terms: termSet.Terms, + // loaded: true, + // expanded: typeof autoExpand !== 'undefined' ? autoExpand : this.state.expanded + // }); + // } else { + // this.setState({ + // terms: [], + // loaded: true + // }); + // } + } + } + + public render(): JSX.Element { + // Specify the inline styling to show or hide the termsets + const styleProps: React.CSSProperties = { + display: this.state.expanded ? 'block' : 'none' + }; + + let termElm: JSX.Element =
; + // Check if the terms have been loaded + + if (this.state.loaded) { + if (this.props.termset.Terms.length > 0) { + termElm = ( +
+ { + this.props.termset.Terms.map(term => { + return ; + }) + } +
+ ); + } else { + termElm =
Term set does not contain any terms
; + } + } else { + termElm = ; + } + + + return ( +
+
+ Expand This Term Set + Menu for Term Set {this.props.termset.Name} +
+
+ {termElm} +
+
+ ); + } +} diff --git a/src/controls/taxonomyPicker/index.ts b/src/controls/taxonomyPicker/index.ts new file mode 100644 index 000000000..d011a5cd1 --- /dev/null +++ b/src/controls/taxonomyPicker/index.ts @@ -0,0 +1,3 @@ +export * from './ITaxonomyPicker'; +export * from './TaxonomyPicker'; +export * from './ITermPicker'; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e3e96a1bd..55c7d4362 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,9 @@ export * from './FileTypeIcon'; export * from './ListView'; export * from './Placeholder'; export * from './SiteBreadcrumb'; +export * from './TaxonomyPicker'; export * from './WebPartTitle'; export * from './IFrameDialog'; - export * from './Common'; export * from './Utilities'; export * from './IFrameDialog'; diff --git a/src/services/ISPTermStorePickerService.ts b/src/services/ISPTermStorePickerService.ts new file mode 100644 index 000000000..636a16553 --- /dev/null +++ b/src/services/ISPTermStorePickerService.ts @@ -0,0 +1,74 @@ +/** + * Interfaces for Term store, groups and term sets + */ +export interface ITermStore { + _ObjectType_: string; // SP.Taxonomy.TermStore + _ObjectIdentity_: string; + Id: string; + Name: string; + Groups: IGroups; +} + +export interface IGroups { + _ObjectType_: string; // SP.Taxonomy.TermGroupCollection + _Child_Items_: IGroup[]; +} + +export interface IGroup { + _ObjectType_: string; // SP.Taxonomy.TermGroup + _ObjectIdentity_: string; + TermSets: ITermSets; + Id: string; + Name: string; + IsSystemGroup: boolean; +} + +export interface ITermSets { + _ObjectType_: string; // SP.Taxonomy.TermSetCollection + _Child_Items_: ITermSet[]; +} + +export interface ITermSet { + _ObjectType_: string; // SP.Taxonomy.TermSet + _ObjectIdentity_: string; + Id: string; + Name: string; + Description: string; + Names: ITermSetNames; + Terms?: ITerm[]; +} + +export interface ITermSetMinimal { + _ObjectType_?: string; // SP.Taxonomy.TermSet + _ObjectIdentity_?: string; + Id: string; + Name: string; +} + +export interface ITermSetNames { + [locale: string]: string; +} + +/** + * Interfaces for the terms + */ +export interface ITerms { + _ObjectType_: string; // SP.Taxonomy.TermCollection + _Child_Items_: ITerm[]; +} + +/** + * Term + */ +export interface ITerm { + _ObjectType_: string; // SP.Taxonomy.Term + _ObjectIdentity_: string; + Id: string; + Name: string; + Description: string; + IsDeprecated: boolean; + IsRoot: boolean; + PathOfTerm: string; + TermSet: ITermSetMinimal; + PathDepth?: number; +} diff --git a/src/services/SPTermStorePickerMockService.ts b/src/services/SPTermStorePickerMockService.ts new file mode 100644 index 000000000..1dcc6ad72 --- /dev/null +++ b/src/services/SPTermStorePickerMockService.ts @@ -0,0 +1,154 @@ +import { ITermStore, ITerm } from './ISPTermStorePickerService'; +import {IPickerTerms, IPickerTerm } from '../controls/taxonomyPicker/ITermPicker'; +/** + * Defines a http client to request mock data to use the web part with the local workbench + */ +export default class SPTermStoreMockHttpClient { + + /** + * Mock SharePoint result sample + */ + private static _mockTermStores: ITermStore[] = [{ + "_ObjectType_": "SP.Taxonomy.TermStore", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:st:generated-idwdg==", + "Id": "\/Guid(fd32e8c4-99f8-402a-8444-4efed3df3076)\/", + "Name": "Mock TermStore", + "Groups": { + "_ObjectType_": "SP.Taxonomy.TermGroupCollection", + "_Child_Items_": [{ + "_ObjectType_": "SP.Taxonomy.TermGroup", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:gr:generated-id-wdsWeHAWewaRChzC1Im8LcS8=", + "Name": "Mock TermGroup 1", + "Id": "\/Guid(051c9ec5-c19e-42a4-8730-b5226f0b712f)\/", + "IsSystemGroup": false, + "TermSets": { + "_ObjectType_": "SP.Taxonomy.TermSetCollection", + "_Child_Items_": [{ + "_ObjectType_": "SP.Taxonomy.TermSet", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:se:generated-id-wdsWeHAWewaRChzC1Im8LcS\u002fwbRtbognrQqP2AGVWYhkx", + "Name": "Mock TermSet 1", + "Id": "\/Guid(5b1b6df0-09a2-42eb-a3f6-006556621931)\/", + "Description": "", + "Names": { + "1033": "Mock TermSet 1" + } + }] + } + }] + } + }]; + + private static _mockTerms: ITerm[] = [{ + "_ObjectType_": "SP.Taxonomy.Term", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:te:generated-id-SPnCDng5nkmdP+UcRJTUTA==", + "Name": "Belgium", + "Id": "0ec2f948-3978-499e-9d3f-e51c4494d44c", + "Description": "", + "IsDeprecated": false, + "IsRoot": true, + "PathOfTerm": "Belgium", + "PathDepth": 1, + "TermSet": { + "_ObjectType_": "SP.Taxonomy.TermSet", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:se:generated-id-", + "Id": "\/Guid(5b1b6df0-09a2-42eb-a3f6-006556621931)\/", + "Name" : "Country" + } + }, { + "_ObjectType_": "SP.Taxonomy.Term", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:te:generated-id-1a3nKkDuZUOvMhLp9PvKFw==", + "Id": "2ae7add5-ee40-4365-af32-12e9f4fbca17", + "Name": "Antwerp", + "Description": "", + "IsDeprecated": false, + "IsRoot": false, + "PathOfTerm": "Belgium;Antwerp", + "PathDepth": 2, + "TermSet": { + "_ObjectType_": "SP.Taxonomy.TermSet", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:se:generated-id-", + "Id": "\/Guid(5b1b6df0-09a2-42eb-a3f6-006556621931)\/", + "Name" : "Country" + } + }, { + "_ObjectType_": "SP.Taxonomy.Term", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:te:generated-id-WCbUI7Ims0ysT\u002fBkk4NUhQ==", + "Name": "Brussels", + "Id": "23d42658-26b2-4cb3-ac4f-f06493835485", + "Description": "", + "IsDeprecated": false, + "IsRoot": false, + "PathOfTerm": "Belgium;Brussels", + "PathDepth": 2, + "TermSet": { + "_ObjectType_": "SP.Taxonomy.TermSet", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:se:generated-id-", + "Id": "\/Guid(5b1b6df0-09a2-42eb-a3f6-006556621931)\/", + "Name" : "Country" + } + }, { + "_ObjectType_": "SP.Taxonomy.Term", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:te:generated-id-WCbUI7Ims0ysT\u002fBkk4NUhQ==", + "Name": "Deprecated", + "Id": "23d42658-26b2-4cb3-ac4f-f06493835486", + "Description": "", + "IsDeprecated": true, + "IsRoot": true, + "PathOfTerm": "Deprecated", + "PathDepth": 1, + "TermSet": { + "_ObjectType_": "SP.Taxonomy.TermSet", + "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:se:generated-id-", + "Id": "\/Guid(5b1b6df0-09a2-42eb-a3f6-006556621931)\/", + "Name" : "Country" + } + }]; + + /** + * Mock method which returns mock terms stores + */ + public static getTermStores(restUrl: string, options?: any): Promise { + return new Promise((resolve) => { + resolve(SPTermStoreMockHttpClient._mockTermStores); + }); + } + + /** + * Mock method wich returns mock terms + */ + public static getAllTerms(): Promise { + return new Promise((resolve) => { + resolve(SPTermStoreMockHttpClient._mockTerms); + }); + } + + + public static searchTermsByName(searchText: string): Promise { + return new Promise((resolve) => { + resolve([ + { + key : "123", + name : 'term1', + path : "path;path2", + termSet :"123", + termSetName : "tsName" + }, + { + key : "124", + name : 'term2', + path : "path", + termSet :"123", + termSetName : "tsName" + }, + { + key : "125", + name : 'term3', + path : "path;path2;path3", + termSet :"123", + termSetName : "tsName" + } + ]); + }); + } + +} diff --git a/src/services/SPTermStorePickerService.ts b/src/services/SPTermStorePickerService.ts new file mode 100644 index 000000000..95efe2ab8 --- /dev/null +++ b/src/services/SPTermStorePickerService.ts @@ -0,0 +1,289 @@ +/** + * DISCLAIMER + * + * As there is not yet an OData end-point for managed metadata, this service makes use of the ProcessQuery end-points. + * The service will get updated once the APIs are in place for managing managed metadata. + */ + +import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http'; +import { Environment, EnvironmentType } from '@microsoft/sp-core-library'; +import { IWebPartContext } from '@microsoft/sp-webpart-base'; +import { ITaxonomyPickerProps } from '../controls/taxonomyPicker/ITaxonomyPicker'; +import { ISPTermStores, ISPTermStore, ISPTermGroups, ISPTermGroup, IPickerTerms, IPickerTerm } from '../controls/taxonomyPicker/ITermPicker'; +import { ITermStore, ITerms, ITerm, IGroup, ITermSet, ITermSets } from './ISPTermStorePickerService'; +import SPTermStoreMockHttpClient from './SPTermStorePickerMockService'; +import TermSet from '../controls/taxonomyPicker/TermSet'; + +/** + * Service implementation to manage term stores in SharePoint + */ +export default class SPTermStorePickerService { + private taxonomySession: string; + private formDigest: string; + private clientServiceUrl: string; + + /** + * Service constructor + */ + constructor(private props: ITaxonomyPickerProps, private context: IWebPartContext) { + if (Environment.type !== EnvironmentType.Local) { + { + this.clientServiceUrl = this.context.pageContext.web.absoluteUrl + '/_vti_bin/client.svc/ProcessQuery'; + } + } + } + + /** + * Gets the collection of term stores in the current SharePoint env + */ + public getTermStores(): Promise { + if (Environment.type === EnvironmentType.Local) { + // If the running environment is local, load the data from the mock + return this.getTermStoresFromMock(); + } else { + // Retrieve the term store name, groups, and term sets + const data = ''; + + const reqHeaders = new Headers(); + reqHeaders.append("accept", "application/json"); + reqHeaders.append("content-type", "application/xml"); + + const httpPostOptions: ISPHttpClientOptions = { + headers: reqHeaders, + body: data + }; + + return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => { + return serviceResponse.json().then((serviceJSONResponse: any) => { + // Construct results + let termStoreResult: ITermStore[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermStore'); + // Check if term store was retrieved + if (termStoreResult.length > 0) { + // Check if the termstore needs to be filtered or limited + if (this.props.termsetNameOrID) { + return termStoreResult.map(termstore => { + let termGroups = termstore.Groups._Child_Items_; + + // Check if the groups have to be limited to a specific term set + if (this.props.termsetNameOrID) { + const termsetNameOrId = this.props.termsetNameOrID; + termGroups = termGroups.map((group: IGroup) => { + group.TermSets._Child_Items_ = group.TermSets._Child_Items_.filter((termSet: ITermSet) => termSet.Name === termsetNameOrId || termSet.Id.toLowerCase() === `/guid(${termsetNameOrId.toLowerCase()})/`); + return group; + }); + } + + // Filter out all systen groups + termGroups = termGroups.filter(group => !group.IsSystemGroup); + + // Filter out empty groups + termGroups = termGroups.filter((group: IGroup) => group.TermSets._Child_Items_.length > 0); + + // Map the new groups + termstore.Groups._Child_Items_ = termGroups; + return termstore; + }); + } + + // Return the term store results + return termStoreResult; + } + return []; + }); + }); + } + } + + /** + * Retrieve all terms for the given term set + * @param termsetId + */ + public async getAllTerms(termsetId: string): Promise { + if (Environment.type === EnvironmentType.Local) { + // If the running environment is local, load the data from the mock + // return this.getAllMockTerms(); + } else { + // Request body to retrieve all terms for the given term set + const data = `${termsetId}`; + + + const reqHeaders = new Headers(); + reqHeaders.append("accept", "application/json"); + reqHeaders.append("content-type", "application/xml"); + + const httpPostOptions: ISPHttpClientOptions = { + headers: reqHeaders, + body: data + }; + + return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => { + return serviceResponse.json().then((serviceJSONResponse: any) => { + + console.log(serviceJSONResponse); + + const termStoreResultTermSets: ITermSet[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermSet'); + console.log(termStoreResultTermSets); + + if (termStoreResultTermSets.length > 0) { + var termStoreResultTermSet = termStoreResultTermSets[0]; + console.log("termset"); + console.log(termStoreResultTermSet); + termStoreResultTermSet.Terms = []; + // Retrieve the term collection results + const termStoreResultTerms: ITerms[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermCollection'); + if (termStoreResultTerms.length > 0) { + // Retrieve all terms + let terms = termStoreResultTerms[0]._Child_Items_; + // Clean the term ID and specify the path depth + terms = terms.map(term => { + term.Id = this._cleanGuid(term.Id); + term['PathDepth'] = term.PathOfTerm.split(';').length; + term.TermSet = { Id : this._cleanGuid(termStoreResultTermSet.Id), Name : termStoreResultTermSet.Name} + + + return term; + }); + // Check if the term set was not empty + if (terms.length > 0) { + // Sort the terms by PathOfTerm + terms = terms.sort(this._sortTerms); + termStoreResultTermSet.Terms = terms; + } + } + return termStoreResultTermSet; + } + return null; + }); + }); + } + } + + + + + /** + * Retrieve all terms that starts with the searchText + * @param searchText + */ + public searchTermsByName(searchText: string): Promise { + if (Environment.type === EnvironmentType.Local) { + // If the running environment is local, load the data from the mock + return SPTermStoreMockHttpClient.searchTermsByName(searchText); + } else { + return this.searchTermsByTermSet(searchText, this.props.termsetNameOrID); + } + } + + /** + * Searches terms for the given term set + * @param searchText + * @param termsetId + */ + private searchTermsByTermSet(searchText: string, termSet: string): Promise { + if (Environment.type === EnvironmentType.Local) { + // If the running environment is local, load the data from the mock + return SPTermStoreMockHttpClient.searchTermsByName(searchText); + } else { + return new Promise(resolve => { + this.getTermStores().then(termStore => { + let TermSetId = termSet; + if (!this.isGuid(termSet)) { + TermSetId = this._cleanGuid(termStore[0].Groups._Child_Items_[0].TermSets._Child_Items_[0].Id); + } + let data = `${searchText}true010true${TermSetId}`; + + const reqHeaders = new Headers(); + reqHeaders.append("accept", "application/json"); + reqHeaders.append("content-type", "application/xml"); + + const httpPostOptions: ISPHttpClientOptions = { + headers: reqHeaders, + body: data + }; + + + return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => { + return serviceResponse.json().then((serviceJSONResponse: any) => { + // Retrieve the term collection results + const termStoreResult: ITerms[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermCollection'); + if (termStoreResult.length > 0) { + // Retrieve all terms + + let terms = termStoreResult[0]._Child_Items_; + + let returnTerms: IPickerTerm[] = []; + terms.forEach(term => { + if (term.Name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1) { + returnTerms.push({ + key:this._cleanGuid(term.Id), + name: term.Name, + path: term.PathOfTerm, + termSet: this._cleanGuid(term.TermSet.Id), + termSetName: term.TermSet.Name + + }); + } + }); + resolve(returnTerms); + } + return null; + }); + }); + + + }); + + + }); + } + } + + private isGuid(strGuid: string): boolean { + return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(strGuid); + } + + /** + * Sort the terms by their path + * @param a term 2 + * @param b term 2 + */ + private _sortTerms(a: ITerm, b: ITerm) { + if (a.PathOfTerm < b.PathOfTerm) { + return -1; + } + if (a.PathOfTerm > b.PathOfTerm) { + return 1; + } + return 0; + } + + /** + * Clean the Guid from the Web Service response + * @param guid + */ + private _cleanGuid(guid: string): string { + if (guid !== undefined) { + return guid.replace('/Guid(', '').replace('/', '').replace(')', ''); + } else { + return ''; + } + } + + /** + * Returns 3 fake SharePoint lists for the Mock mode + */ + private getTermStoresFromMock(): Promise { + return SPTermStoreMockHttpClient.getTermStores(this.context.pageContext.web.absoluteUrl).then((data) => { + return data; + }) as Promise; + } + + /** + * Returns 3 fake SharePoint lists for the Mock mode + */ + private getAllMockTerms(): Promise { + return SPTermStoreMockHttpClient.getAllTerms().then((data) => { + return data; + }) as Promise; + } +} diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 9c395765b..eef252973 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -11,6 +11,8 @@ import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from '../. import { SPHttpClient } from '@microsoft/sp-http'; import { SiteBreadcrumb } from '../../../SiteBreadcrumb'; import { WebPartTitle } from '../../../WebPartTitle'; +import { TaxonomyPicker, IPickerTerms } from '../../../TaxonomyPicker'; + import { IFrameDialog } from '../../../IFrameDialog'; import { Environment, EnvironmentType } from '@microsoft/sp-core-library'; @@ -70,6 +72,15 @@ export default class ControlsTest extends React.Component
+ +
TaxonomyPicker tester: + +
iframe dialog tester: Date: Sun, 15 Apr 2018 15:37:17 +0200 Subject: [PATCH 15/35] AnchorId Implementation --- .../taxonomyPicker/ITaxonomyPicker.ts | 3 +- .../taxonomyPicker/TaxonomyPicker.tsx | 4 +- src/controls/taxonomyPicker/TermSet.tsx | 45 ++++++++++++++++--- src/services/ISPTermStorePickerService.ts | 1 + src/services/SPTermStorePickerService.ts | 11 ++--- .../controlsTest/components/ControlsTest.tsx | 1 + 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts index 1c9d69049..70ac6cc83 100644 --- a/src/controls/taxonomyPicker/ITaxonomyPicker.ts +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -94,10 +94,11 @@ export interface ITermSetProps extends ITermChanges { termset: ITermSet; autoExpand: () => void; multiSelection: boolean; + anchorId? : string; } export interface ITermSetState { - + loaded?: boolean; expanded?: boolean; } diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index a7430502f..de64e6ff3 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -28,7 +28,7 @@ export const COLLAPSED_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8A export const EXPANDED_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAUCAYAAABSx2cSAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjEwcrIlkgAAAFtJREFUOE9j/P//PwPZAKSZXEy2RrCLybV1CGjetWvX/46ODqBLUQOXoJ9BGtXU1MCYJM0wjZGRkaRpRtZIkmZ0jSRpBgUOzJ8wmqwAw5eICIb2qGYSkyfNAgwAasU+UQcFvD8AAAAASUVORK5CYII='; // /_layouts/15/images/MDNExpanded.png export const GROUP_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAC9SURBVDhPY2CgNXh1qEkdiJ8D8X90TNBuJM0V6IpBhoHFgIxebKYTIwYzAMNpxGhGdsFwNoBgNEFjAWsYgOSKiorMgPgbEP/Hgj8AxXpB0Yg1gQAldYuLix8/efLkzn8s4O7du9eAan7iM+DV/v37z546der/jx8/sJkBdhVOA5qbm08ePnwYrOjQoUOkGwDU+AFowLmjR4/idwGukAYaYAkMgxfPnj27h816kDg4DPABoAI/IP6DIxZA4l0AOd9H3QXl5+cAAAAASUVORK5CYII='; // /_layouts/15/Images/EMMGroup.png export const TERMSET_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACaSURBVDhPrZLRCcAgDERdpZMIjuQA7uWH4CqdxMY0EQtNjKWB0A/77sxF55SKMTalk8a61lqCFqsLiwKac84ZRUUBi7MoYHVmAfjfjzE6vJqZQfie0AcwBQVW8ATi7AR7zGGGNSE6Q2cyLSPIjRswjO7qKhcPDN2hK46w05wZMcEUIG+HrzzcrRsQBIJ5hS8C9fGAPmRwu/9RFxW6L8CM4Ry8AAAAAElFTkSuQmCC'; // /_layouts/15/Images/EMMTermSet.png - +export const TERM_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACzSURBVDhPY2AYNKCoqIgTiOcD8X8S8F6wB4Aa1IH4akNDw+mPHz++/E8EuHTp0jmQRSDNCcXFxa/XrVt3gAh9KEpgBvx/9OjRLVI1g9TDDYBp3rlz5//Kysr/IJoYgGEASPPatWsbQDQxAMOAbdu2gZ0FookBcAOePHlyhxgN6GqQY+Hdhg0bDpJqCNgAaDrQAnJuNDY2nvr06dMbYgw6e/bsabgBUEN4yEiJ2wdNViLfIQC3sTh2vtJcswAAAABJRU5ErkJggg=='; /** * Renders the controls for PropertyFieldTermPicker component @@ -275,7 +275,7 @@ export class TaxonomyPicker extends React.Component{this.state.termSetAndTerms.Name} {/* */} - +
diff --git a/src/controls/taxonomyPicker/TermSet.tsx b/src/controls/taxonomyPicker/TermSet.tsx index 2a3c970c4..965501d64 100644 --- a/src/controls/taxonomyPicker/TermSet.tsx +++ b/src/controls/taxonomyPicker/TermSet.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; import { ITermSetProps, ITermSetState } from './ITaxonomyPicker'; import { ITerm, ITermSet } from '../../services/ISPTermStorePickerService'; -import { EXPANDED_IMG, COLLAPSED_IMG, TERMSET_IMG } from './TaxonomyPicker'; +import { EXPANDED_IMG, COLLAPSED_IMG, TERMSET_IMG, TERM_IMG } from './TaxonomyPicker'; import Term from './Term'; import styles from './TaxonomyPicker.module.scss'; @@ -11,9 +11,13 @@ import styles from './TaxonomyPicker.module.scss'; * Term set component */ export default class TermSet extends React.Component { + + private _terms : ITerm[]; + private _anchorName : string; + constructor(props: ITermSetProps) { super(props); - + this._terms = this.props.termset.Terms; this.state = { loaded: true, expanded: true @@ -74,6 +78,37 @@ export default class TermSet extends React.Component t.Id.toLowerCase() === this.props.anchorId.toLowerCase()).shift(); + console.log(anchorTerm) + if (anchorTerm) + { + const anchorDepth = anchorTerm.PathDepth; + this._anchorName = anchorTerm.Name; + var anchorTerms : ITerm[] = this._terms.filter(t => t.PathOfTerm.substring(0, anchorTerm.PathOfTerm.length) === anchorTerm.PathOfTerm && t.Id !== anchorTerm.Id); + + anchorTerms = anchorTerms.map(term => { + console.log(term.PathDepth); + term.PathDepth = term.PathDepth - anchorTerm.PathDepth; + console.log(term.PathDepth); + + return term; + }); + + console.log(anchorTerms); + this._terms = anchorTerms; + console.log(this._terms); + } + } + } + + public render(): JSX.Element { // Specify the inline styling to show or hide the termsets const styleProps: React.CSSProperties = { @@ -84,11 +119,11 @@ export default class TermSet extends React.Component 0) { + if (this._terms.length > 0) { termElm = (
{ - this.props.termset.Terms.map(term => { + this._terms.map(term => { return ; }) } @@ -106,7 +141,7 @@ export default class TermSet extends React.Component
Expand This Term Set - Menu for Term Set {this.props.termset.Name} + Menu for Term Set {this.props.anchorId ? this._anchorName : this.props.termset.Name}
{termElm} diff --git a/src/services/ISPTermStorePickerService.ts b/src/services/ISPTermStorePickerService.ts index 636a16553..39e1d43dd 100644 --- a/src/services/ISPTermStorePickerService.ts +++ b/src/services/ISPTermStorePickerService.ts @@ -71,4 +71,5 @@ export interface ITerm { PathOfTerm: string; TermSet: ITermSetMinimal; PathDepth?: number; + ParentId?: string; } diff --git a/src/services/SPTermStorePickerService.ts b/src/services/SPTermStorePickerService.ts index 95efe2ab8..7ddb2ae5d 100644 --- a/src/services/SPTermStorePickerService.ts +++ b/src/services/SPTermStorePickerService.ts @@ -135,12 +135,15 @@ export default class SPTermStorePickerService { // Retrieve all terms let terms = termStoreResultTerms[0]._Child_Items_; // Clean the term ID and specify the path depth + console.log(terms); terms = terms.map(term => { term.Id = this._cleanGuid(term.Id); term['PathDepth'] = term.PathOfTerm.split(';').length; - term.TermSet = { Id : this._cleanGuid(termStoreResultTermSet.Id), Name : termStoreResultTermSet.Name} - - + term.TermSet = { Id : this._cleanGuid(termStoreResultTermSet.Id), Name : termStoreResultTermSet.Name}; + if (term["Parent"]) + { + term.ParentId = this._cleanGuid(term["Parent"].Id); + } return term; }); // Check if the term set was not empty @@ -159,8 +162,6 @@ export default class SPTermStorePickerService { } - - /** * Retrieve all terms that starts with the searchText * @param searchText diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index eef252973..eb2da8511 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -186,6 +186,7 @@ export default class ControlsTest extends React.Component Date: Mon, 16 Apr 2018 08:45:45 +0200 Subject: [PATCH 16/35] Cleaned up Unused objects and interfaces --- .../taxonomyPicker/ITaxonomyPicker.ts | 6 +- src/controls/taxonomyPicker/ITermPicker.ts | 61 +----------------- .../taxonomyPicker/TaxonomyPicker.tsx | 16 ++--- src/controls/taxonomyPicker/Term.tsx | 1 - src/controls/taxonomyPicker/TermGroup.tsx | 63 ------------------- .../{TermSet.tsx => TermParent.tsx} | 57 ++--------------- src/services/SPTermStorePickerMockService.ts | 54 +++++++++------- src/services/SPTermStorePickerService.ts | 10 +-- .../controlsTest/components/ControlsTest.tsx | 2 +- 9 files changed, 51 insertions(+), 219 deletions(-) delete mode 100644 src/controls/taxonomyPicker/TermGroup.tsx rename src/controls/taxonomyPicker/{TermSet.tsx => TermParent.tsx} (67%) diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts index 70ac6cc83..2e241cd9b 100644 --- a/src/controls/taxonomyPicker/ITaxonomyPicker.ts +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -53,12 +53,10 @@ export interface ITaxonomyPickerProps { * */ onGetErrorMessage?: (value: IPickerTerms) => string | Promise; + /** - * Custom Field will start to validate after users stop typing for `deferredValidationTime` milliseconds. - * Default value is 200. + * onChange Event */ - deferredValidationTime?: number; - onChange?: (newValue?: IPickerTerms) => void; } diff --git a/src/controls/taxonomyPicker/ITermPicker.ts b/src/controls/taxonomyPicker/ITermPicker.ts index 2e0cd3147..ee2d6c055 100644 --- a/src/controls/taxonomyPicker/ITermPicker.ts +++ b/src/controls/taxonomyPicker/ITermPicker.ts @@ -15,52 +15,6 @@ export interface IPickerTerm { export interface IPickerTerms extends Array { } -/** - * Generic Term Object (abstract interface) - */ -export interface ISPTermObject { - Name: string; - Guid: string; - Identity: string; - leaf: boolean; - children?: ISPTermObject[]; - collapsed?: boolean; - type: string; -} - -/** - * Defines a SharePoint Term Store - */ -export interface ISPTermStore extends ISPTermObject { - IsOnline: boolean; - WorkingLanguage: string; - DefaultLanguage: string; - Languages: string[]; -} - -/** - * Defines an array of Term Stores - */ -export interface ISPTermStores extends Array { -} - -/** - * Defines a Term Store Group of term sets - */ -export interface ISPTermGroup extends ISPTermObject { - IsSiteCollectionGroup: boolean; - IsSystemGroup: boolean; - CreatedDate: string; - LastModifiedDate: string; -} - -/** - * Array of Term Groups - */ -export interface ISPTermGroups extends Array { -} - - /** * Public properties of the PropertyFieldTermPicker custom field */ @@ -134,19 +88,6 @@ export interface IPropertyFieldTermPickerProps { * Default value is 200. */ deferredValidationTime?: number; - /** - * Specifies if you want to show or hide the term store name from the panel - */ - hideTermStoreName?: boolean; + } -/** - * Private properties of the PropertyFieldTermPicker custom field. - * We separate public & private properties to include onRender & onDispose method waited - * by the PropertyFieldCustom, witout asking to the developer to add it when he's using - * the PropertyFieldTermPicker. - */ -// export interface IPropertyFieldTermPickerPropsInternal extends IPropertyFieldTermPickerProps { -// // // onRender(elem: HTMLElement): void; -// // // onDispose(elem: HTMLElement): void; -// } diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index de64e6ff3..444e1b3a2 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -15,8 +15,8 @@ import { ITaxonomyPickerProps, ITaxonomyPickerState, ITermGroupProps, ITermGroup import SPTermStorePickerService from './../../services/SPTermStorePickerService'; import { ITermSet, IGroup, ITerm } from './../../services/ISPTermStorePickerService'; import styles from './TaxonomyPicker.module.scss'; -import { sortBy, uniqBy } from '@microsoft/sp-lodash-subset'; -import TermSet from './TermSet'; +import { sortBy, uniqBy, cloneDeep } from '@microsoft/sp-lodash-subset'; +import TermParent from './TermParent'; import FieldErrorMessage from './ErrorMessage'; import * as appInsights from '../../common/appInsights'; @@ -71,10 +71,8 @@ export class TaxonomyPicker extends React.Component { - console.log(response); // Check if a response was retrieved if (response !== null) { - console.log(response); this.setState({ termSetAndTerms: response, loaded: true @@ -97,7 +95,7 @@ export class TaxonomyPicker extends React.Component : '' } - - - {this.state.loaded === true && - -

{this.state.termSetAndTerms.Name}

- {/* */} - +
diff --git a/src/controls/taxonomyPicker/Term.tsx b/src/controls/taxonomyPicker/Term.tsx index 5fe964179..66d8a396e 100644 --- a/src/controls/taxonomyPicker/Term.tsx +++ b/src/controls/taxonomyPicker/Term.tsx @@ -27,7 +27,6 @@ export default class Term extends React.Component { * Handle the checkbox change trigger */ private _handleChange(ev: React.FormEvent, isChecked: boolean) { - console.log("handling change"); this.setState({ selected: isChecked }); diff --git a/src/controls/taxonomyPicker/TermGroup.tsx b/src/controls/taxonomyPicker/TermGroup.tsx deleted file mode 100644 index 186acc33c..000000000 --- a/src/controls/taxonomyPicker/TermGroup.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// import * as React from 'react'; -// import { ITermGroupProps, ITermGroupState } from './ITaxonomyPicker'; -// import { GROUP_IMG, EXPANDED_IMG, COLLAPSED_IMG } from './TaxonomyPicker'; -// import TermSet from './TermSet'; - -// import styles from './TaxonomyPicker.module.scss'; - -// /** -// * Term group component -// */ -// export default class TermGroup extends React.Component { -// constructor(props: ITermGroupProps) { -// super(props); - -// this.state = { -// expanded: false -// }; - -// this._handleClick = this._handleClick.bind(this); -// this._autoExpand = this._autoExpand.bind(this); -// } - -// /** -// * Handle the click event: collapse or expand -// */ -// private _handleClick() { -// this.setState({ -// expanded: !this.state.expanded -// }); -// } - -// /** -// * Function to auto expand the termset -// */ -// private _autoExpand() { -// this.setState({ -// expanded: true -// }); -// } - -// public render(): JSX.Element { -// // Specify the inline styling to show or hide the termsets -// const styleProps: React.CSSProperties = { -// display: this.state.expanded ? 'block' : 'none' -// }; - -// return ( -//
-//
-// Expand This Node -// Menu for Group {this.props.group.Name} -//
-//
-// { -// this.props.group.TermSets._Child_Items_.map(termset => { -// return ; -// }) -// } -//
-//
-// ); -// } -// } diff --git a/src/controls/taxonomyPicker/TermSet.tsx b/src/controls/taxonomyPicker/TermParent.tsx similarity index 67% rename from src/controls/taxonomyPicker/TermSet.tsx rename to src/controls/taxonomyPicker/TermParent.tsx index 965501d64..1919beab0 100644 --- a/src/controls/taxonomyPicker/TermSet.tsx +++ b/src/controls/taxonomyPicker/TermParent.tsx @@ -8,9 +8,9 @@ import Term from './Term'; import styles from './TaxonomyPicker.module.scss'; /** - * Term set component + * Term Parent component, represents termset or term if anchorId */ -export default class TermSet extends React.Component { +export default class TermParent extends React.Component { private _terms : ITerm[]; private _anchorName : string; @@ -22,24 +22,10 @@ export default class TermSet extends React.Component node.termSet === this.props.termset.Id); - if (selectedTermsInSet.length > 0) { - this._autoLoadTerms(); - } - this._handleClick = this._handleClick.bind(this); - this._loadTerms = this._loadTerms.bind(this); } - /** - * Autoload the terms of the term set - */ - private _autoLoadTerms() { - // this.props.autoExpand(); - this._loadTerms(true); - } + /** * Handle the click event: collapse or expand @@ -48,45 +34,18 @@ export default class TermSet extends React.Component t.Id.toLowerCase() === this.props.anchorId.toLowerCase()).shift(); - console.log(anchorTerm) if (anchorTerm) { const anchorDepth = anchorTerm.PathDepth; @@ -94,16 +53,12 @@ export default class TermSet extends React.Component t.PathOfTerm.substring(0, anchorTerm.PathOfTerm.length) === anchorTerm.PathOfTerm && t.Id !== anchorTerm.Id); anchorTerms = anchorTerms.map(term => { - console.log(term.PathDepth); term.PathDepth = term.PathDepth - anchorTerm.PathDepth; - console.log(term.PathDepth); return term; }); - console.log(anchorTerms); this._terms = anchorTerms; - console.log(this._terms); } } } diff --git a/src/services/SPTermStorePickerMockService.ts b/src/services/SPTermStorePickerMockService.ts index 1dcc6ad72..08149c754 100644 --- a/src/services/SPTermStorePickerMockService.ts +++ b/src/services/SPTermStorePickerMockService.ts @@ -1,4 +1,4 @@ -import { ITermStore, ITerm } from './ISPTermStorePickerService'; +import { ITermStore, ITerm, ITermSet } from './ISPTermStorePickerService'; import {IPickerTerms, IPickerTerm } from '../controls/taxonomyPicker/ITermPicker'; /** * Defines a http client to request mock data to use the web part with the local workbench @@ -38,7 +38,15 @@ export default class SPTermStoreMockHttpClient { } }]; - private static _mockTerms: ITerm[] = [{ + private static _mockTerms: ITermSet = + {"_ObjectType_":"SP.Taxonomy.TermSet", + "_ObjectIdentity_":"a4f45d9e-7003-5000-7d35-b4064108885e|fec14c62-7c3b-481b-851b-c80d7802b224:se:15WaN9o+nUi6qkivmCMKhxA4k0b3ed9BqbnSqve6DjrKW1tjX4wxSIv4oNnV63Xg", + "Id":"/Guid(635b5bca-8c5f-4831-8bf8-a0d9d5eb75e0)/", + "Name":"Countries", + "Description":"", +"Names":{"1033":"Countries"}, +"Terms":[ + { "_ObjectType_": "SP.Taxonomy.Term", "_ObjectIdentity_": "5e06ddd0-d2dd-4fff-bcc0-42b40f4aa59e|4dbeb936-1813-4630-a4bd-9811df3fe7f1:te:generated-id-SPnCDng5nkmdP+UcRJTUTA==", "Name": "Belgium", @@ -102,7 +110,7 @@ export default class SPTermStoreMockHttpClient { "Id": "\/Guid(5b1b6df0-09a2-42eb-a3f6-006556621931)\/", "Name" : "Country" } - }]; + }]}; /** * Mock method which returns mock terms stores @@ -116,39 +124,41 @@ export default class SPTermStoreMockHttpClient { /** * Mock method wich returns mock terms */ - public static getAllTerms(): Promise { - return new Promise((resolve) => { + public static getAllTerms(): Promise { + return new Promise((resolve) => { resolve(SPTermStoreMockHttpClient._mockTerms); }); } - public static searchTermsByName(searchText: string): Promise { return new Promise((resolve) => { resolve([ { - key : "123", - name : 'term1', - path : "path;path2", - termSet :"123", - termSetName : "tsName" + key : "23d42658-26b2-4cb3-ac4f-f06493835485", + name : 'Brussels', + path : "Belgium;Brussels", + termSet :"635b5bca-8c5f-4831-8bf8-a0d9d5eb75e0", + termSetName : "Countries" }, { - key : "124", - name : 'term2', - path : "path", - termSet :"123", - termSetName : "tsName" + key : "2ae7add5-ee40-4365-af32-12e9f4fbca17", + name : 'Antwerp', + path : "Belgium;Antwerp", + termSet :"635b5bca-8c5f-4831-8bf8-a0d9d5eb75e0", + termSetName : "Countries" }, { - key : "125", - name : 'term3', - path : "path;path2;path3", - termSet :"123", - termSetName : "tsName" + key : "0ec2f948-3978-499e-9d3f-e51c4494d44c", + name : 'Belgium', + path : "Belgium", + termSet :"635b5bca-8c5f-4831-8bf8-a0d9d5eb75e0", + termSetName : "Countries" } ]); }); } + + + } + -} diff --git a/src/services/SPTermStorePickerService.ts b/src/services/SPTermStorePickerService.ts index 7ddb2ae5d..f8e7cb71d 100644 --- a/src/services/SPTermStorePickerService.ts +++ b/src/services/SPTermStorePickerService.ts @@ -9,10 +9,10 @@ import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@micro import { Environment, EnvironmentType } from '@microsoft/sp-core-library'; import { IWebPartContext } from '@microsoft/sp-webpart-base'; import { ITaxonomyPickerProps } from '../controls/taxonomyPicker/ITaxonomyPicker'; -import { ISPTermStores, ISPTermStore, ISPTermGroups, ISPTermGroup, IPickerTerms, IPickerTerm } from '../controls/taxonomyPicker/ITermPicker'; +import { IPickerTerms, IPickerTerm } from '../controls/taxonomyPicker/ITermPicker'; import { ITermStore, ITerms, ITerm, IGroup, ITermSet, ITermSets } from './ISPTermStorePickerService'; import SPTermStoreMockHttpClient from './SPTermStorePickerMockService'; -import TermSet from '../controls/taxonomyPicker/TermSet'; + /** * Service implementation to manage term stores in SharePoint @@ -101,7 +101,7 @@ export default class SPTermStorePickerService { public async getAllTerms(termsetId: string): Promise { if (Environment.type === EnvironmentType.Local) { // If the running environment is local, load the data from the mock - // return this.getAllMockTerms(); + return this.getAllMockTerms(); } else { // Request body to retrieve all terms for the given term set const data = `${termsetId}`; @@ -282,9 +282,9 @@ export default class SPTermStorePickerService { /** * Returns 3 fake SharePoint lists for the Mock mode */ - private getAllMockTerms(): Promise { + private getAllMockTerms(): Promise { return SPTermStoreMockHttpClient.getAllTerms().then((data) => { return data; - }) as Promise; + }) as Promise; } } diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index eb2da8511..41ee96613 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -186,7 +186,7 @@ export default class ControlsTest extends React.Component Date: Mon, 16 Apr 2018 08:50:49 +0200 Subject: [PATCH 17/35] Removed a few more unused objects --- src/controls/taxonomyPicker/ITaxonomyPicker.ts | 14 ++------------ src/controls/taxonomyPicker/TaxonomyPicker.tsx | 2 +- src/controls/taxonomyPicker/TermParent.tsx | 6 +++--- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts index 2e241cd9b..52ea54d35 100644 --- a/src/controls/taxonomyPicker/ITaxonomyPicker.ts +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -77,25 +77,15 @@ export interface ITermChanges { activeNodes?: IPickerTerms; } -export interface ITermGroupProps extends ITermChanges { - group: IGroup; - termstore: string; - termsService: SPTermStorePickerService; - multiSelection: boolean; -} - -export interface ITermGroupState { - expanded: boolean; -} -export interface ITermSetProps extends ITermChanges { +export interface ITermParentProps extends ITermChanges { termset: ITermSet; autoExpand: () => void; multiSelection: boolean; anchorId? : string; } -export interface ITermSetState { +export interface ITermParentState { loaded?: boolean; expanded?: boolean; diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index 444e1b3a2..70b86b47c 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -11,7 +11,7 @@ import TermPicker from './TermPicker'; import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers'; import { IPickerTerms, IPickerTerm } from './ITermPicker'; -import { ITaxonomyPickerProps, ITaxonomyPickerState, ITermGroupProps, ITermGroupState, ITermSetProps, ITermSetState, ITermProps, ITermState } from './ITaxonomyPicker'; +import { ITaxonomyPickerProps, ITaxonomyPickerState, ITermParentProps, ITermParentState, ITermProps, ITermState } from './ITaxonomyPicker'; import SPTermStorePickerService from './../../services/SPTermStorePickerService'; import { ITermSet, IGroup, ITerm } from './../../services/ISPTermStorePickerService'; import styles from './TaxonomyPicker.module.scss'; diff --git a/src/controls/taxonomyPicker/TermParent.tsx b/src/controls/taxonomyPicker/TermParent.tsx index 1919beab0..732c8841d 100644 --- a/src/controls/taxonomyPicker/TermParent.tsx +++ b/src/controls/taxonomyPicker/TermParent.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; -import { ITermSetProps, ITermSetState } from './ITaxonomyPicker'; +import { ITermParentProps, ITermParentState } from './ITaxonomyPicker'; import { ITerm, ITermSet } from '../../services/ISPTermStorePickerService'; import { EXPANDED_IMG, COLLAPSED_IMG, TERMSET_IMG, TERM_IMG } from './TaxonomyPicker'; import Term from './Term'; @@ -10,12 +10,12 @@ import styles from './TaxonomyPicker.module.scss'; /** * Term Parent component, represents termset or term if anchorId */ -export default class TermParent extends React.Component { +export default class TermParent extends React.Component { private _terms : ITerm[]; private _anchorName : string; - constructor(props: ITermSetProps) { + constructor(props: ITermParentProps) { super(props); this._terms = this.props.termset.Terms; this.state = { From 99bd3334a1b886d2c6c174f59094c099b5895900 Mon Sep 17 00:00:00 2001 From: David Opdendries Date: Mon, 16 Apr 2018 08:56:50 +0200 Subject: [PATCH 18/35] spelling mistake --- src/TaxonomyPicker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TaxonomyPicker.ts b/src/TaxonomyPicker.ts index c704d8d8c..1015b7705 100644 --- a/src/TaxonomyPicker.ts +++ b/src/TaxonomyPicker.ts @@ -1 +1 @@ -export * from './controls/TaxonomyPicker/index'; +export * from './controls/taxonomyPicker/index'; From fa28b5467a963d91eb83faa3ca148da5094eed81 Mon Sep 17 00:00:00 2001 From: David Opdendries Date: Mon, 16 Apr 2018 12:20:53 +0200 Subject: [PATCH 19/35] One last spelling mistake. --- src/controls/taxonomyPicker/TermPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controls/taxonomyPicker/TermPicker.tsx b/src/controls/taxonomyPicker/TermPicker.tsx index 4331f0fec..7ad28f5a1 100644 --- a/src/controls/taxonomyPicker/TermPicker.tsx +++ b/src/controls/taxonomyPicker/TermPicker.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers'; import { IPickerTerm, IPickerTerms } from './ITermPicker'; import SPTermStorePickerService from './../../services/SPTermStorePickerService'; -import styles from './TaxonomyPicker.Module.scss'; +import styles from './TaxonomyPicker.module.scss'; import { ITaxonomyPickerProps } from './ITaxonomyPicker'; import { IWebPartContext } from '@microsoft/sp-webpart-base'; From 50870ba99a3caa3b66084a42fb40b94c3d2b8da5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 17 Apr 2018 13:08:47 +0300 Subject: [PATCH 20/35] TermPicker fixes --- config/tslint.json | 5 +- docs/documentation/docs/beta.md | 17 +++++ docs/documentation/docs/index.md | 3 +- docs/documentation/mkdocs.yml | 1 + .../taxonomyPicker/ITaxonomyPicker.ts | 6 +- .../taxonomyPicker/TaxonomyPicker.tsx | 18 ++--- src/controls/taxonomyPicker/TermPicker.tsx | 65 +++++++++-------- src/services/SPTermStorePickerService.ts | 70 +++++++++++++++---- .../controlsTest/components/ControlsTest.tsx | 6 +- 9 files changed, 127 insertions(+), 64 deletions(-) create mode 100644 docs/documentation/docs/beta.md diff --git a/config/tslint.json b/config/tslint.json index 0bb934c20..c22006217 100644 --- a/config/tslint.json +++ b/config/tslint.json @@ -39,7 +39,8 @@ "use-named-parameter": true, "valid-typeof": true, "variable-name": false, - "whitespace": false + "whitespace": false, + "no-debugger": true } } -} \ No newline at end of file +} diff --git a/docs/documentation/docs/beta.md b/docs/documentation/docs/beta.md new file mode 100644 index 000000000..c493fdb70 --- /dev/null +++ b/docs/documentation/docs/beta.md @@ -0,0 +1,17 @@ +# Testing out a beta release ![](https://img.shields.io/npm/v/@pnp/spfx-controls-react/next.svg) + +All you need to do for testing out a beta release of `@pnp/spfx-controls-react` is to install the dependency as follows: + +``` +npm install @pnp/spfx-controls-react@next --save +``` + +## Beta control documentation + +The control documentation is only live for public releases, not for beta versions. If you want to checkout the markdown files of all controls in the `dev` branch: [beta documentation](https://github.com/SharePoint/sp-dev-fx-controls-react/tree/dev/docs/documentation/docs/controls). + +## Next Steps + +Once you installed the beta version, you can start using the controls in your solution. Go to the homepage to get an overview of all the available controls and the steps to get started: [home](./). + +![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/beta) diff --git a/docs/documentation/docs/index.md b/docs/documentation/docs/index.md index 92720b5d4..14ae85f81 100644 --- a/docs/documentation/docs/index.md +++ b/docs/documentation/docs/index.md @@ -2,7 +2,8 @@ This repository provides developers with a set of reusable React controls that can be used in SharePoint Framework (SPFx) solutions. The project provides controls for building web parts and extensions. -!!! attention The controls project has a minimal dependency on SharePoint Framework version `1.3.0`. Be aware that the controls might not work in solutions your building for on-premises. As for on-premises solutions version `1.1.0` is currently used. +!!! attention + The controls project has a minimal dependency on SharePoint Framework version `1.3.0`. Be aware that the controls might not work in solutions your building for on-premises. As for on-premises solutions version `1.1.0` is currently used. ## Getting started diff --git a/docs/documentation/mkdocs.yml b/docs/documentation/mkdocs.yml index 1c7fe23b0..c470808a6 100644 --- a/docs/documentation/mkdocs.yml +++ b/docs/documentation/mkdocs.yml @@ -21,6 +21,7 @@ pages: - FieldTitleRenderer: 'controls/fields/FieldTitleRenderer.md' - FieldUrlRenderer: 'controls/fields/FieldUrlRenderer.md' - FieldUserRenderer: 'controls/fields/FieldUserRenderer.md' + - 'Beta testing': 'beta.md' - About: - 'Release notes': 'about/release-notes.md' - License: 'about/license.md' diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts index 52ea54d35..6afb7de73 100644 --- a/src/controls/taxonomyPicker/ITaxonomyPicker.ts +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -31,7 +31,7 @@ export interface ITaxonomyPickerProps { * Limit the terms that can be picked by the Term Set name or ID */ termsetNameOrID: string; - /** + /** * Id of a child term in the termset where to be able to selected and search the terms from */ ancoreId?: string; @@ -64,7 +64,7 @@ export interface ITaxonomyPickerProps { * PropertyFieldTermPickerHost state interface */ export interface ITaxonomyPickerState { - + termSetAndTerms? : ITermSet; errorMessage?: string; openPanel?: boolean; @@ -86,7 +86,7 @@ export interface ITermParentProps extends ITermChanges { } export interface ITermParentState { - + loaded?: boolean; expanded?: boolean; } diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index 70b86b47c..e37c001c4 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -4,12 +4,10 @@ import { Async } from 'office-ui-fabric-react/lib/Utilities'; import { PrimaryButton, DefaultButton, IconButton, IButtonProps } from 'office-ui-fabric-react/lib/Button'; import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; -//import { IPropertyFieldTermPickerPropsInternal } from './ITermPicker'; import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http'; import { Label } from 'office-ui-fabric-react/lib/Label'; import TermPicker from './TermPicker'; import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers'; - import { IPickerTerms, IPickerTerm } from './ITermPicker'; import { ITaxonomyPickerProps, ITaxonomyPickerState, ITermParentProps, ITermParentState, ITermProps, ITermState } from './ITaxonomyPicker'; import SPTermStorePickerService from './../../services/SPTermStorePickerService'; @@ -48,7 +46,6 @@ export class TaxonomyPicker extends React.Component : '' } - {this.state.loaded === true && - -
- -

{this.state.termSetAndTerms.Name}

+ { + this.state.loaded === true && this.state.termSetAndTerms && ( +
+

{this.state.termSetAndTerms.Name}

-
- - +
+ ) } -
); diff --git a/src/controls/taxonomyPicker/TermPicker.tsx b/src/controls/taxonomyPicker/TermPicker.tsx index 7ad28f5a1..a02affd1f 100644 --- a/src/controls/taxonomyPicker/TermPicker.tsx +++ b/src/controls/taxonomyPicker/TermPicker.tsx @@ -58,18 +58,22 @@ export default class TermPicker extends React.Component) { - return (
- {term.item.name} - {!term.disabled && - - - - } -
); + return ( +
+ {term.item.name} + { + !term.disabled && ( + + + + ) + } +
+ ); } /** @@ -84,23 +88,32 @@ export default class TermPicker extends React.Component -
{term.name}
-
in {termParent}
-
); + return ( +
+
{term.name}
+
in {termParent}
+
+ ); } /** * When Filter Changes a new search for suggestions */ - private onFilterChanged(filterText: string, tagList: IPickerTerm[]): Promise { - + private async onFilterChanged(filterText: string, tagList: IPickerTerm[]): Promise { if (filterText !== "") { let termsService = new SPTermStorePickerService(this.props.termPickerHostProps, this.props.context); - let terms = termsService.searchTermsByName(filterText); - return terms; + let terms = await termsService.searchTermsByName(filterText); + // Filter out the terms which are already set + const filteredTerms = []; + for (const term of terms) { + if (tagList.filter(tag => tag.key === term.key).length === 0) { + filteredTerms.push(term); + } + } + return filteredTerms; + } else { + return Promise.resolve([]); } - } @@ -115,14 +128,6 @@ export default class TermPicker extends React.Component
diff --git a/src/services/SPTermStorePickerService.ts b/src/services/SPTermStorePickerService.ts index f8e7cb71d..2bb55887d 100644 --- a/src/services/SPTermStorePickerService.ts +++ b/src/services/SPTermStorePickerService.ts @@ -96,13 +96,27 @@ export default class SPTermStorePickerService { /** * Retrieve all terms for the given term set - * @param termsetId + * @param termset */ - public async getAllTerms(termsetId: string): Promise { + public async getAllTerms(termset: string): Promise { if (Environment.type === EnvironmentType.Local) { // If the running environment is local, load the data from the mock return this.getAllMockTerms(); } else { + let termsetId: string = termset; + // Check if the provided term set property is a GUID or string + if (!this.isGuid(termset)) { + // Fetch the term store information + const termStore = await this.getTermStores(); + // Get the ID of the provided term set name + termsetId = this.getTermSetId(termStore, termset); + if (termsetId) { + termsetId = this._cleanGuid(termsetId); + } else { + return null; + } + } + // Request body to retrieve all terms for the given term set const data = `${termsetId}`; @@ -118,16 +132,10 @@ export default class SPTermStorePickerService { return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => { return serviceResponse.json().then((serviceJSONResponse: any) => { - - console.log(serviceJSONResponse); - const termStoreResultTermSets: ITermSet[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermSet'); - console.log(termStoreResultTermSets); - + if (termStoreResultTermSets.length > 0) { var termStoreResultTermSet = termStoreResultTermSets[0]; - console.log("termset"); - console.log(termStoreResultTermSet); termStoreResultTermSet.Terms = []; // Retrieve the term collection results const termStoreResultTerms: ITerms[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermCollection'); @@ -135,7 +143,6 @@ export default class SPTermStorePickerService { // Retrieve all terms let terms = termStoreResultTerms[0]._Child_Items_; // Clean the term ID and specify the path depth - console.log(terms); terms = terms.map(term => { term.Id = this._cleanGuid(term.Id); term['PathDepth'] = term.PathOfTerm.split(';').length; @@ -163,7 +170,36 @@ export default class SPTermStorePickerService { /** - * Retrieve all terms that starts with the searchText + * Get the term set ID by its name + * @param termstore + * @param termset + */ + private getTermSetId(termstore: ITermStore[], termsetName: string): string { + if (termstore && termstore.length > 0 && termsetName) { + // Get the first term store + const ts = termstore[0]; + // Check if the term store contains groups + if (ts.Groups && ts.Groups._Child_Items_) { + for (const group of ts.Groups._Child_Items_) { + // Check if the group contains term sets + if (group.TermSets && group.TermSets._Child_Items_) { + for (const termSet of group.TermSets._Child_Items_) { + // Check if the term set is found + if (termSet.Name === termsetName) { + return termSet.Id; + } + } + } + } + } + } + + return null; + } + + + /** + * Retrieve all terms that starts with the searchText * @param searchText */ public searchTermsByName(searchText: string): Promise { @@ -189,8 +225,16 @@ export default class SPTermStorePickerService { this.getTermStores().then(termStore => { let TermSetId = termSet; if (!this.isGuid(termSet)) { - TermSetId = this._cleanGuid(termStore[0].Groups._Child_Items_[0].TermSets._Child_Items_[0].Id); + // Get the ID of the provided term set name + TermSetId = this.getTermSetId(termStore, termSet); + if (TermSetId) { + TermSetId = this._cleanGuid(TermSetId); + } else { + resolve(null); + return; + } } + let data = `${searchText}true010true${TermSetId}`; const reqHeaders = new Headers(); @@ -209,7 +253,7 @@ export default class SPTermStorePickerService { const termStoreResult: ITerms[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermCollection'); if (termStoreResult.length > 0) { // Retrieve all terms - + let terms = termStoreResult[0]._Child_Items_; let returnTerms: IPickerTerm[] = []; diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 7f112bc6a..57efbb2dd 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -197,9 +197,9 @@ export default class ControlsTest extends React.ComponentTaxonomyPicker tester: Date: Tue, 17 Apr 2018 13:21:36 +0300 Subject: [PATCH 21/35] Version update for 1.3.0 + extra group check in termstore fetching --- CHANGELOG.md | 6 ++++++ docs/documentation/docs/about/release-notes.md | 8 +++++++- package.json | 2 +- scripts/sync-changelogs.js | 4 ++++ src/services/SPTermStorePickerService.ts | 2 +- 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 scripts/sync-changelogs.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6afe0c82b..c445fb534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Releases +## 1.3.0 + +**New Controls** + +- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) + ## 1.2.5 **Fixes** diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index 2787eff18..c445fb534 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -1,5 +1,11 @@ # Releases +## 1.3.0 + +**New Controls** + +- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) + ## 1.2.5 **Fixes** @@ -52,7 +58,7 @@ ## 1.1.2 -- Fix for WebPartTitle control to inherit color +- Fix for `WebPartTitle` control to inherit color - Improved telemetry with some object checks ## 1.1.1 diff --git a/package.json b/package.json index 4a30c51ed..87da2a093 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@pnp/spfx-controls-react", "description": "Reusable React controls for SharePoint Framework solutions", - "version": "1.2.5", + "version": "1.3.0", "engines": { "node": ">=0.10.0" }, diff --git a/scripts/sync-changelogs.js b/scripts/sync-changelogs.js new file mode 100644 index 000000000..93d823e8e --- /dev/null +++ b/scripts/sync-changelogs.js @@ -0,0 +1,4 @@ +const fs = require('fs'); +const path = require('path'); +const changelog = fs.readFileSync(path.join(__dirname, '../CHANGELOG.md'), 'utf8'); +fs.writeFileSync(path.join(__dirname, '../docs/documentation/docs/about/release-notes.md'), changelog, 'utf-8'); diff --git a/src/services/SPTermStorePickerService.ts b/src/services/SPTermStorePickerService.ts index 2bb55887d..6e2d10a09 100644 --- a/src/services/SPTermStorePickerService.ts +++ b/src/services/SPTermStorePickerService.ts @@ -68,7 +68,7 @@ export default class SPTermStorePickerService { if (this.props.termsetNameOrID) { const termsetNameOrId = this.props.termsetNameOrID; termGroups = termGroups.map((group: IGroup) => { - group.TermSets._Child_Items_ = group.TermSets._Child_Items_.filter((termSet: ITermSet) => termSet.Name === termsetNameOrId || termSet.Id.toLowerCase() === `/guid(${termsetNameOrId.toLowerCase()})/`); + group.TermSets._Child_Items_ = group.TermSets._Child_Items_.filter((termSet: ITermSet) => termSet.Name === termsetNameOrId || this._cleanGuid(termSet.Id).toLowerCase() === this._cleanGuid(termsetNameOrId).toLowerCase()); return group; }); } From 879ae383dfd44322848b5ed39d9e1bb1daa9131c Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 17 Apr 2018 14:24:42 +0300 Subject: [PATCH 22/35] Fix for #63 --- CHANGELOG.md | 2 +- src/controls/taxonomyPicker/TaxonomyPicker.tsx | 9 ++++++++- src/webparts/controlsTest/components/ControlsTest.tsx | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c445fb534..7404c2eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ **New Controls** -- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) +- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) ## 1.2.5 diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index e37c001c4..306b9beb0 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -129,6 +129,8 @@ export class TaxonomyPicker extends React.Component

{this.state.termSetAndTerms.Name}

- + ) } diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 57efbb2dd..59b2e4a60 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -197,7 +197,7 @@ export default class ControlsTest extends React.ComponentTaxonomyPicker tester: Date: Tue, 17 Apr 2018 14:26:13 +0300 Subject: [PATCH 23/35] Sync changelog --- docs/documentation/docs/about/release-notes.md | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index c445fb534..7404c2eba 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -4,7 +4,7 @@ **New Controls** -- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) +- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) ## 1.2.5 diff --git a/package.json b/package.json index 87da2a093..6bdc5f6af 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test": "gulp test", "prepublishOnly": "gulp", "versionUpdater": "gulp versionUpdater", - "karma": "karma start --circle true" + "karma": "karma start --circle true", + "changelog": "node scripts/sync-changelogs.js" }, "dependencies": { "@pnp/common": "^1.0.1", From 2b66cf00ac7414bb8a620abccadee6790404d0f8 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 17 Apr 2018 15:28:44 +0300 Subject: [PATCH 24/35] Add a contextual menu to the ListView --- .../docs/controls/ListView.ContextualMenu.md | 159 +++++++++--------- docs/documentation/docs/controls/ListView.md | 5 +- docs/documentation/mkdocs.yml | 1 + 3 files changed, 80 insertions(+), 85 deletions(-) diff --git a/docs/documentation/docs/controls/ListView.ContextualMenu.md b/docs/documentation/docs/controls/ListView.ContextualMenu.md index 28ec3e77a..6d1e68568 100644 --- a/docs/documentation/docs/controls/ListView.ContextualMenu.md +++ b/docs/documentation/docs/controls/ListView.ContextualMenu.md @@ -1,26 +1,11 @@ -# Add a contextual menu -## The ListView column -To add a contextual menu to our ListView we will insert another Viewfield in the webpart code at the position of our choice, for instance after the “Lastname”: -```TypeScript -{ - name: "", - sorting: false, - maxWidth: 40, - render: (rowitem: IListitem) => { - const element:React.ReactElement =React.createElement( - ECB, { - item:rowitem - } - ); - return element; - } -} -``` -We use the render method of IViewField. Inside we create our ECB component and as a property we handover the rowitem which is clicked. -We could also handover functions that shall be executed inside the context menu component but be able to be bound to the parent component, that is the webpart/component which contains the ListView. +# ListView: Add a contextual menu + ## The ContextualMenu component -We now need to create the ECB component which represents our context menu. Therefore we will use a combination of an [IconButton](https://developer.microsoft.com/en-us/fabric#/components/button#Variants) and a [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) from the Office UI Fabric components. -The code for this additional component looks like this: + +In order to create a contextual menu for your list view, you first need to create a new component which will use a combination of an [IconButton](https://developer.microsoft.com/en-us/fabric#/components/button#Variants) and [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) controls from the Office UI Fabric React. + +Here is some sample code: + ```TypeScript import * as React from 'react'; import { Layer, IconButton, IButtonProps } from 'office-ui-fabric-react'; @@ -33,81 +18,91 @@ import { IListitem } from '../../model/IListitem'; export class ECB extends React.Component { public constructor(props: IECBProps) { - super(props); + super(props); - this.state = { - panelOpen: false - }; + this.state = { + panelOpen: false + }; } public render() { - return
- console.error('Disabled action should not be clickable.') - } - ] - } } - /> -
- ; + return ( +
+ console.error('Disabled action should not be clickable.') + } + ] + }} /> +
+ ); } private handleClick(source:string, event) { - alert(source + ' clicked'); + alert(`${source} clicked`); } } ``` -One of the things to mention is that in the (totally simplified for demo reasons here) onClick function we hand over one attribute of the clicked item that we have in the components' properties so we can process it in the function. -Another trick is to give an empty iconName for the menuIconProps. This is because once you attach a menuProps Attribute to any Kind of Office UI fabric button a ChevronDown selector on the right side of the button will occur by default. -With the menuIconProps Attribute you can adjust this, that is when specifying an empty Name you can remove it. This is what we want because we only want to have our “MoreVertical” icon which are the three dots in a vertical order. -To place this a bit more centric, we have small CSS manipulation as well: -```SCSS -.ecb { - position: absolute; - top: -3px; - .ecbbutton div { - padding-left: 12px; - } + +## The ListView column + +Once the ECB component is created, you can add the contextual menu to the `ListView` control. In order to do this, you have to insert another `Viewfield` in code at the position of our choice. For instance after the `Lastname`: + +```TypeScript +{ + name: "", + sorting: false, + maxWidth: 40, + render: (rowitem: IListitem) => { + const element:React.ReactElement = React.createElement( + ECB, + { + item: rowitem + } + ); + return element; + } } ``` + +Inside the render method of the `IViewField`, the ECB component gets created and the current item will be used as a reference for the clicked row. + ## The result The result will look like the following: ![ContextualMenu_shown](../assets/ListView.ContextualMenu.png) -And in action it shows which function and item was clicked: + +Once you click on an action, you will see the alert: ![ContextualMenu_clicked](../assets/ListView.ContextualMenu_clicked.png) diff --git a/docs/documentation/docs/controls/ListView.md b/docs/documentation/docs/controls/ListView.md index 3dac85910..e1a47b614 100644 --- a/docs/documentation/docs/controls/ListView.md +++ b/docs/documentation/docs/controls/ListView.md @@ -54,9 +54,8 @@ const groupByFields: IGrouping[] = [ ]; ``` -## Extend with a ContextualMenu - -To extend the ListView control with a [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) refer to [ListView.ContextualMenu](./ListView.ContextualMenu.md) +!!! note "Extend ListView with a ContextualMenu" + To extend the `ListView` control with a [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) refer to [ListView.ContextualMenu](./ListView.ContextualMenu). ## Implementation diff --git a/docs/documentation/mkdocs.yml b/docs/documentation/mkdocs.yml index c470808a6..1f4ea3e99 100644 --- a/docs/documentation/mkdocs.yml +++ b/docs/documentation/mkdocs.yml @@ -4,6 +4,7 @@ pages: - Controls: - FileTypeIcon: 'controls/FileTypeIcon.md' - ListView: 'controls/ListView.md' + - "ListView: add a contextual menu": 'controls/ListView.ContextualMenu.md' - Placeholder: 'controls/Placeholder.md' - SiteBreadcrumb: 'controls/SiteBreadcrumb.md' - WebPartTitle: 'controls/WebPartTitle.md' From d15d356d7073cf4e86542a87eaedebc410b0ce3d Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 17 Apr 2018 18:03:30 +0300 Subject: [PATCH 25/35] Locale strings + TermSet selection (#64) --- .../taxonomyPicker/ITaxonomyPicker.ts | 9 ++- .../taxonomyPicker/TaxonomyPicker.module.scss | 17 +++++- .../taxonomyPicker/TaxonomyPicker.tsx | 33 +++++++--- src/controls/taxonomyPicker/TermParent.tsx | 61 +++++++++++++------ src/controls/taxonomyPicker/TermPicker.tsx | 23 ++++++- src/loc/en-us.ts | 8 ++- src/loc/mystrings.d.ts | 7 +++ src/services/SPTermStorePickerService.ts | 48 +++++++++------ .../controlsTest/components/ControlsTest.tsx | 1 + 9 files changed, 151 insertions(+), 56 deletions(-) diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts index 6afb7de73..9e4be97ca 100644 --- a/src/controls/taxonomyPicker/ITaxonomyPicker.ts +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -35,6 +35,10 @@ export interface ITaxonomyPickerProps { * Id of a child term in the termset where to be able to selected and search the terms from */ ancoreId?: string; + /** + * Specify if the term set itself is selectable in the tree view + */ + isTermSetSelectable?: boolean; /** * Whether the property pane field is enabled or not. */ @@ -80,9 +84,12 @@ export interface ITermChanges { export interface ITermParentProps extends ITermChanges { termset: ITermSet; - autoExpand: () => void; multiSelection: boolean; anchorId? : string; + isTermSetSelectable?: boolean; + + autoExpand: () => void; + termSetSelectedChange?: (termSet: ITermSet, isChecked: boolean) => void; } export interface ITermParentState { diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.module.scss b/src/controls/taxonomyPicker/TaxonomyPicker.module.scss index c67d90b20..5956cc045 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.module.scss +++ b/src/controls/taxonomyPicker/TaxonomyPicker.module.scss @@ -34,6 +34,17 @@ margin-left: 15px; } +.termSetSelectable { + height: 50px; + line-height: 50px; +} + +.termSetSelector { + display: inline-block; + margin: 0 8px 0 4px; + vertical-align: middle; +} + .term { padding-left: 20px; @@ -72,8 +83,8 @@ width: 100%; text-align: left; cursor: pointer; - - + + .termSuggestionSubTitle { font-size: 12px; @@ -129,7 +140,7 @@ display: flex; align-items: center; } - + .errorIcon { font-size: 14px; margin-right: 5px; diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index 306b9beb0..dfa543f1b 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { IWebPartContext } from '@microsoft/sp-webpart-base'; -import { Async } from 'office-ui-fabric-react/lib/Utilities'; import { PrimaryButton, DefaultButton, IconButton, IButtonProps } from 'office-ui-fabric-react/lib/Button'; import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; @@ -32,7 +31,6 @@ export const TERM_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQC * Renders the controls for PropertyFieldTermPicker component */ export class TaxonomyPicker extends React.Component { - private async: Async; private delayedValidate: (value: IPickerTerms) => void; private termsService: SPTermStorePickerService; private previousValues: IPickerTerms = []; @@ -58,7 +56,6 @@ export class TaxonomyPicker extends React.Component { + const ts: ITermSet = {...termSet}; + // Clean /Guid.../ from the ID + ts.Id = this.termsService.cleanGuid(ts.Id); + // Create a term for the termset + const term: ITerm = { + Name: ts.Name, + Id: ts.Id, + TermSet: ts, + PathOfTerm: "", + _ObjectType_: ts._ObjectType_, + _ObjectIdentity_: ts._ObjectIdentity_, + Description: ts.Description, + IsDeprecated: null, + IsRoot: null + }; + + // Trigger the normal change event + this.termsChanged(term, isChecked); } /** @@ -227,6 +241,7 @@ export class TaxonomyPicker extends React.Component @@ -268,6 +283,8 @@ export class TaxonomyPicker extends React.Component diff --git a/src/controls/taxonomyPicker/TermParent.tsx b/src/controls/taxonomyPicker/TermParent.tsx index 732c8841d..1cd044ae8 100644 --- a/src/controls/taxonomyPicker/TermParent.tsx +++ b/src/controls/taxonomyPicker/TermParent.tsx @@ -6,6 +6,8 @@ import { EXPANDED_IMG, COLLAPSED_IMG, TERMSET_IMG, TERM_IMG } from './TaxonomyPi import Term from './Term'; import styles from './TaxonomyPicker.module.scss'; +import { Checkbox } from 'office-ui-fabric-react'; +import * as strings from 'ControlStrings'; /** * Term Parent component, represents termset or term if anchorId @@ -17,6 +19,7 @@ export default class TermParent extends React.Component t.PathOfTerm.substring(0, anchorTerm.PathOfTerm.length) === anchorTerm.PathOfTerm && t.Id !== anchorTerm.Id); - + anchorTerms = anchorTerms.map(term => { term.PathDepth = term.PathDepth - anchorTerm.PathDepth; - + return term; }); @@ -64,6 +55,24 @@ export default class TermParent extends React.Component, isChecked: boolean): void => { + this.props.termSetSelectedChange(this.props.termset, isChecked); + } + + public render(): JSX.Element { // Specify the inline styling to show or hide the termsets const styleProps: React.CSSProperties = { @@ -72,7 +81,7 @@ export default class TermParent extends React.Component; // Check if the terms have been loaded - + if (this.state.loaded) { if (this._terms.length > 0) { termElm = ( @@ -85,18 +94,30 @@ export default class TermParent extends React.Component ); } else { - termElm =
Term set does not contain any terms
; + termElm =
{strings.TaxonomyPickerNoTerms}
; } } else { termElm = ; } - + return (
-
- Expand This Term Set - Menu for Term Set {this.props.anchorId ? this._anchorName : this.props.termset.Name} +
+ {strings.TaxonomyPickerExpandTitle} + { + // Show the termset selection box + (!this.props.anchorId && this.props.isTermSetSelectable) && + a.path === "" && a.key === a.termSet).length >= 1} + onChange={this.termSetSelectionChange} /> + } + {strings.TaxonomyPickerMenuTermSet} + { + this.props.anchorId ? + this._anchorName : + this.props.termset.Name + }
{termElm} diff --git a/src/controls/taxonomyPicker/TermPicker.tsx b/src/controls/taxonomyPicker/TermPicker.tsx index a02affd1f..06914f9af 100644 --- a/src/controls/taxonomyPicker/TermPicker.tsx +++ b/src/controls/taxonomyPicker/TermPicker.tsx @@ -5,6 +5,7 @@ import SPTermStorePickerService from './../../services/SPTermStorePickerService' import styles from './TaxonomyPicker.module.scss'; import { ITaxonomyPickerProps } from './ITaxonomyPicker'; import { IWebPartContext } from '@microsoft/sp-webpart-base'; +import * as strings from 'ControlStrings'; export class TermBasePicker extends BasePicker> { @@ -20,8 +21,9 @@ export interface ITermPickerProps { context: IWebPartContext; disabled: boolean; value: IPickerTerms; - onChanged: (items: IPickerTerm[]) => void; allowMultipleSelections : boolean; + isTermSetSelectable?: boolean; + onChanged: (items: IPickerTerm[]) => void; } export default class TermPicker extends React.Component { @@ -91,7 +93,7 @@ export default class TermPicker extends React.Component
{term.name}
-
in {termParent}
+
{strings.TaxonomyPickerInLabel} {termParent ? termParent : strings.TaxonomyPickerTermSetLabel}
); } @@ -102,7 +104,22 @@ export default class TermPicker extends React.Component { if (filterText !== "") { let termsService = new SPTermStorePickerService(this.props.termPickerHostProps, this.props.context); - let terms = await termsService.searchTermsByName(filterText); + let terms: IPickerTerm[] = await termsService.searchTermsByName(filterText); + // Check if the termset can be selected + if (this.props.isTermSetSelectable) { + // Retrieve the current termset + const termSet = await termsService.getTermSet(); + // Check if termset was retrieved and if it contains the filter value + if (termSet && termSet.Name.toLowerCase().indexOf(filterText.toLowerCase()) === 0) { + // Add the termset to the suggestion list + terms.push({ + key: termsService.cleanGuid(termSet.Id), + name: termSet.Name, + path: "", + termSet: termsService.cleanGuid(termSet.Id) + }); + } + } // Filter out the terms which are already set const filteredTerms = []; for (const term of terms) { diff --git a/src/loc/en-us.ts b/src/loc/en-us.ts index c4cdff11c..18ad597bd 100644 --- a/src/loc/en-us.ts +++ b/src/loc/en-us.ts @@ -38,6 +38,12 @@ define([], () => { "SendEmailTo": "Send an email to {0}", "StartChatWith": "Start a chat with {0}", "Contact": "Contact", - "UpdateProfile": "Update your profile" + "UpdateProfile": "Update your profile", + + "TaxonomyPickerNoTerms": "Term set does not contain any terms", + "TaxonomyPickerExpandTitle": "Expand This Term Set", + "TaxonomyPickerMenuTermSet": "Menu for Term Set", + "TaxonomyPickerInLabel": "in", + "TaxonomyPickerTermSetLabel": "Term Set" }; }); diff --git a/src/loc/mystrings.d.ts b/src/loc/mystrings.d.ts index cec683743..74482a912 100644 --- a/src/loc/mystrings.d.ts +++ b/src/loc/mystrings.d.ts @@ -8,6 +8,13 @@ declare interface IControlStrings { StartChatWith: string; Contact: string; UpdateProfile: string; + + // Taxonomy picker + TaxonomyPickerNoTerms: string; + TaxonomyPickerExpandTitle: string; + TaxonomyPickerMenuTermSet: string; + TaxonomyPickerInLabel: string; + TaxonomyPickerTermSetLabel: string; } declare module 'ControlStrings' { diff --git a/src/services/SPTermStorePickerService.ts b/src/services/SPTermStorePickerService.ts index 6e2d10a09..b2592c25d 100644 --- a/src/services/SPTermStorePickerService.ts +++ b/src/services/SPTermStorePickerService.ts @@ -68,7 +68,7 @@ export default class SPTermStorePickerService { if (this.props.termsetNameOrID) { const termsetNameOrId = this.props.termsetNameOrID; termGroups = termGroups.map((group: IGroup) => { - group.TermSets._Child_Items_ = group.TermSets._Child_Items_.filter((termSet: ITermSet) => termSet.Name === termsetNameOrId || this._cleanGuid(termSet.Id).toLowerCase() === this._cleanGuid(termsetNameOrId).toLowerCase()); + group.TermSets._Child_Items_ = group.TermSets._Child_Items_.filter((termSet: ITermSet) => termSet.Name === termsetNameOrId || this.cleanGuid(termSet.Id).toLowerCase() === this.cleanGuid(termsetNameOrId).toLowerCase()); return group; }); } @@ -94,6 +94,19 @@ export default class SPTermStorePickerService { } } + /** + * Gets the current term set + */ + public async getTermSet(): Promise { + if (Environment.type === EnvironmentType.Local) { + const termSetInfo = await SPTermStoreMockHttpClient.getAllTerms(); + return termSetInfo; + } else { + const termStore = await this.getTermStores(); + return this.getTermSetId(termStore, this.props.termsetNameOrID); + } + } + /** * Retrieve all terms for the given term set * @param termset @@ -109,9 +122,9 @@ export default class SPTermStorePickerService { // Fetch the term store information const termStore = await this.getTermStores(); // Get the ID of the provided term set name - termsetId = this.getTermSetId(termStore, termset); - if (termsetId) { - termsetId = this._cleanGuid(termsetId); + const crntTermSet = this.getTermSetId(termStore, termset); + if (crntTermSet) { + termsetId = this.cleanGuid(crntTermSet.Id); } else { return null; } @@ -144,12 +157,12 @@ export default class SPTermStorePickerService { let terms = termStoreResultTerms[0]._Child_Items_; // Clean the term ID and specify the path depth terms = terms.map(term => { - term.Id = this._cleanGuid(term.Id); + term.Id = this.cleanGuid(term.Id); term['PathDepth'] = term.PathOfTerm.split(';').length; - term.TermSet = { Id : this._cleanGuid(termStoreResultTermSet.Id), Name : termStoreResultTermSet.Name}; + term.TermSet = { Id : this.cleanGuid(termStoreResultTermSet.Id), Name : termStoreResultTermSet.Name}; if (term["Parent"]) { - term.ParentId = this._cleanGuid(term["Parent"].Id); + term.ParentId = this.cleanGuid(term["Parent"].Id); } return term; }); @@ -174,7 +187,7 @@ export default class SPTermStorePickerService { * @param termstore * @param termset */ - private getTermSetId(termstore: ITermStore[], termsetName: string): string { + private getTermSetId(termstore: ITermStore[], termsetName: string): ITermSet { if (termstore && termstore.length > 0 && termsetName) { // Get the first term store const ts = termstore[0]; @@ -186,7 +199,7 @@ export default class SPTermStorePickerService { for (const termSet of group.TermSets._Child_Items_) { // Check if the term set is found if (termSet.Name === termsetName) { - return termSet.Id; + return termSet; } } } @@ -226,9 +239,9 @@ export default class SPTermStorePickerService { let TermSetId = termSet; if (!this.isGuid(termSet)) { // Get the ID of the provided term set name - TermSetId = this.getTermSetId(termStore, termSet); - if (TermSetId) { - TermSetId = this._cleanGuid(TermSetId); + const crntTermSet = this.getTermSetId(termStore, termSet); + if (crntTermSet) { + TermSetId = this.cleanGuid(crntTermSet.Id); } else { resolve(null); return; @@ -260,12 +273,11 @@ export default class SPTermStorePickerService { terms.forEach(term => { if (term.Name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1) { returnTerms.push({ - key:this._cleanGuid(term.Id), + key:this.cleanGuid(term.Id), name: term.Name, path: term.PathOfTerm, - termSet: this._cleanGuid(term.TermSet.Id), + termSet: this.cleanGuid(term.TermSet.Id), termSetName: term.TermSet.Name - }); } }); @@ -274,11 +286,7 @@ export default class SPTermStorePickerService { return null; }); }); - - }); - - }); } } @@ -306,7 +314,7 @@ export default class SPTermStorePickerService { * Clean the Guid from the Web Service response * @param guid */ - private _cleanGuid(guid: string): string { + public cleanGuid(guid: string): string { if (guid !== undefined) { return guid.replace('/Guid(', '').replace('/', '').replace(')', ''); } else { diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 59b2e4a60..702247f08 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -204,6 +204,7 @@ export default class ControlsTest extends React.Component
iframe dialog tester: From e015db4b395fbf1dbc29fe94944029b07c754a1a Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 17 Apr 2018 18:09:27 +0300 Subject: [PATCH 26/35] Changelog updates --- CHANGELOG.md | 2 +- docs/documentation/docs/about/release-notes.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7404c2eba..83b7e93f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ **New Controls** -- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) +- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) [#64](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/64) ## 1.2.5 diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index 7404c2eba..83b7e93f0 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -4,7 +4,7 @@ **New Controls** -- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) +- `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) [#64](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/64) ## 1.2.5 From 5474e43d2aad8fb3ecde2ebdd0f469c59967bcff Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 17 Apr 2018 18:16:48 +0300 Subject: [PATCH 27/35] Typo in the locale file --- src/loc/en-us.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loc/en-us.ts b/src/loc/en-us.ts index 18ad597bd..f443af6df 100644 --- a/src/loc/en-us.ts +++ b/src/loc/en-us.ts @@ -41,7 +41,7 @@ define([], () => { "UpdateProfile": "Update your profile", "TaxonomyPickerNoTerms": "Term set does not contain any terms", - "TaxonomyPickerExpandTitle": "Expand This Term Set", + "TaxonomyPickerExpandTitle": "Expand this Term Set", "TaxonomyPickerMenuTermSet": "Menu for Term Set", "TaxonomyPickerInLabel": "in", "TaxonomyPickerTermSetLabel": "Term Set" From 1bec453bfb6e1192fc74b0562cfaa85d4dfc5586 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 18 Apr 2018 18:20:48 +0300 Subject: [PATCH 28/35] Fix for #65 --- CHANGELOG.md | 4 ++++ .../documentation/docs/about/release-notes.md | 4 ++++ src/controls/listView/ListView.tsx | 24 +++++++++++-------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b7e93f0..5886d14c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) [#64](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/64) +**Fixes** + +- Issue fixed when the optional `selection` property was not provided to the `ListView` [#65](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/65) + ## 1.2.5 **Fixes** diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index 83b7e93f0..5886d14c4 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -6,6 +6,10 @@ - `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) [#64](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/64) +**Fixes** + +- Issue fixed when the optional `selection` property was not provided to the `ListView` [#65](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/65) + ## 1.2.5 **Fixes** diff --git a/src/controls/listView/ListView.tsx b/src/controls/listView/ListView.tsx index 2804bafde..00df6b637 100644 --- a/src/controls/listView/ListView.tsx +++ b/src/controls/listView/ListView.tsx @@ -60,7 +60,9 @@ export class ListView extends React.Component { if (!isEqual(prevProps, this.props)) { // Reset the selected items - this._selection.setItems(this.props.items, true); + if (this._selection) { + this._selection.setItems(this.props.items, true); + } // Process list view properties this._processProperties(); } @@ -353,15 +355,17 @@ export class ListView extends React.Component { const sortedItems = descending ? ascItems.reverse() : ascItems; // Check if selection needs to be updated - const selection = this._selection.getSelection(); - if (selection && selection.length > 0) { - // Clear selection - this._selection.setItems([], true); - setTimeout(() => { - // Find new index - let idxs: number[] = selection.map(item => findIndex(sortedItems, item)); - idxs.forEach(idx => this._selection.setIndexSelected(idx, true, false)); - }, 0); + if (this._selection) { + const selection = this._selection.getSelection(); + if (selection && selection.length > 0) { + // Clear selection + this._selection.setItems([], true); + setTimeout(() => { + // Find new index + let idxs: number[] = selection.map(item => findIndex(sortedItems, item)); + idxs.forEach(idx => this._selection.setIndexSelected(idx, true, false)); + }, 0); + } } // Return the sorted items list From a84f5f743302e9251895522febdf93da4f174452 Mon Sep 17 00:00:00 2001 From: David Opdendries Date: Thu, 19 Apr 2018 09:35:48 +0200 Subject: [PATCH 29/35] Taxonomy Picker Documentation. anchorId spelling mistake --- .../docs/assets/TermPicker-autocomplete.png | Bin 0 -> 16521 bytes .../docs/assets/TermPicker-selection.png | Bin 0 -> 36400 bytes .../docs/assets/termpicker-empty.png | Bin 0 -> 1112 bytes .../docs/assets/termpicker-limit-to-group.png | Bin 0 -> 20955 bytes .../docs/assets/termpicker-selected-terms.png | Bin 0 -> 3930 bytes .../docs/controls/TaxonomyPicker.md | 87 ++++++++++++++++++ docs/documentation/docs/index.md | 1 + docs/documentation/mkdocs.yml | 1 + .../taxonomyPicker/ITaxonomyPicker.ts | 2 +- .../taxonomyPicker/TaxonomyPicker.tsx | 2 +- .../controlsTest/components/ControlsTest.tsx | 4 +- 11 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 docs/documentation/docs/assets/TermPicker-autocomplete.png create mode 100644 docs/documentation/docs/assets/TermPicker-selection.png create mode 100644 docs/documentation/docs/assets/termpicker-empty.png create mode 100644 docs/documentation/docs/assets/termpicker-limit-to-group.png create mode 100644 docs/documentation/docs/assets/termpicker-selected-terms.png create mode 100644 docs/documentation/docs/controls/TaxonomyPicker.md diff --git a/docs/documentation/docs/assets/TermPicker-autocomplete.png b/docs/documentation/docs/assets/TermPicker-autocomplete.png new file mode 100644 index 0000000000000000000000000000000000000000..18b317203844637c568dad3a760e94f19669c3fa GIT binary patch literal 16521 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

^rQKjkb$RK!A9=jfV29jOzPy>m9Kh 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 literal 0 HcmV?d00001 diff --git a/docs/documentation/docs/assets/TermPicker-selection.png b/docs/documentation/docs/assets/TermPicker-selection.png new file mode 100644 index 0000000000000000000000000000000000000000..fa45a5634c45dec3bd7d77a4c9f342634429ce36 GIT binary patch literal 36400 zcmeFZXHZn@*Dpw>$xY5VH9-l2B7#8EWJEFuNS2I%qM{%H$5Hec?onX0Ker_Qm_wtMeqt@TT5#~K=F(NJI2tL_yvip+7&edg4#z^M-HU$YYJcOYqtpq zxMT4D53`1tb9ufevAvs9DcwamjN$F|Nk%lzwMgHMp-&7rO4XpI6HsC(hCw1 z6BjG-M>@-`x}TqEX5{8pzPB-3Zc+W_;XjsZR8;ZV@}5;zjk@D6Qn6UheWSFb8~m+udJ%N>E?zt;b348@Q`WS`}GChqfw#e%d5{nJX3ycD#YlEi=pFemD&G1wI67*W-rL)onUljRX(|2Y4F9*6d_BFC#piwI zxHvMl-IxExGUx9+4>`HNvtlInT=B(~92ZAN**mMIVpkCd2ZNce6@P!vYpMSIWqw?x z`q_BZ(-6gkH7=%xHxhE{UESD7GOJJ5;U2Z&xSUT(MMXtR`&d?on3;@(q%+p|GLN93 zAU8L+nBav{rCevOL_$3==7Pe)gNr=;><0Zxl)nD{xe9)`Qj?d)e-w5nE3HG0cUhH) z*wyYnRlKu0S(#({WT!Cn_Ky$0zBEgrP>7q|Ge^zy!Jcz%t)?%-+uM&HpRqnVI?A)Z ze*Npw!5^t>b>Fs^-~Idhn~sj|@8%}Ty2%TJA^Gs}!_!Oh^76&+Q(acaOZ)r#KfS#A zWSc_!#J6*{^)@*z7dA!Gwr%>uhYzEpzCS+}lTtD%_%0A66hMXL{Oia9@t&I?Q z`Aj)bT!lU7;_dJ724iQ20{?D&ee*ET$N-_^lb1l9?>SZFTb{0y?fs=m{^sm{eQ~JKkdyG%F2R09iv#SILm77O+{zE+T!>0hJgls&!<5F+$kaE^({v=aetdYiIxgnz{p-B<%+c}R zdE=iUYJ+4~b=a9}?)EnsfA3Ju!;Zrh5MEY^n2;gy|31u~^6>aj-h2`&kmWNbtS;?- zZx)&HeFY8<4mR6va#qSH0gEadL_l$n(#2cfq~+vx{`^^+t}QfWx;j>%;de(_NvTMQ zUx@qdpq=f{l@+h~HxK*bm?tMEE$J^^x}?XvWv^USRb|@|{7|f#=y5w%xp+9FOI*S3 zO;E9JM(VbBgZCHnRw_1@K&JWCtGvb`3YH(7Xp?2d^V-xR$l%I#M^TAN4!o&z9Z6EP z9b!a(Oi5rW&vWRBzHoEaZLENF@+&ll6y2qcfa=oH*o@x?r?=qXUAni?^6Hg9L3(!C z=F!3J$Slh~y-JlozdKG96%>N@H`U}KzB6Qu{}tSgiP#dHV{$h`hOMry>asJBULLEq z?5)(og*2%mbX;ZMCC12a4`oQaypr?6rX9*LATaQ`av;mz#h2YbKfQ#atTi?m=Quez z`90sEr7GTdGW<48l%5%lE4XH8n7rR=@hau8rz=&Zl=o6+hVg2LkeLDEEt~IE7oP9K zS?4?_B~#{J-}?cz*uBLXm`;} zoQ|CDme%Z<8>W^`UZ3G?7bQ>%Y9?s!N0BF+szfILzVh8l+`QcU{oA)XMQ6^O`8qp` zQQS>O8KIAKo_u=?=YI0LvB-7pFN;WYFh=%?CG?it--pFM!VNjxTqK^{70A&QH!ay* z|6I=$^3b&0{7gkxu|9*n_p_Bx(G4k0J^YbJSm(2~H^06zI|$@IckY~oM7jH?7qRQC zkx@}PrKiR%z78m_lcNLUla9!kn0S&s+~T5xlapXwtfPlVS!Ly!8*j22xM}&nX+iJv z@b+%^`ds%!%1%>D3&y;Fp+@FK`_54KW@W)4w?7u?7#SHY4LmyC`I&}_#bSMZeOIPy zm*65{D9)eaE}T%Yo(($M-zw0EPq#ZyNkao=CE~pL^QV|DbbIM&==6(bM-Nqy3=DN4 zrziJ*&&7JB2xuht^QH7hmmV2NkVWw8WSypF_YnGBbz@lLFWoNM%QOu{5B?Q#Ix+A3 z((J>38Gn}b^zPibv%)MKK}N$N<496xTo_=VDC5*W+Y!Q>kHTrS80+bI9`5{{C^KWE zK}HHNDBa&KTIGEeEdbMHV|CTT!6Co2l;VuQQSQatE^tbVS+-QO>s{WXy~Bt|YYHFn zs+gA;5{^~n=jXfmLLGaV6<{brLPCldkDETR>SxLJ_4W#tb}%R77DG3_z8-<$1uq(( zoO|~~YM{$e3I0z|E2-jIy$kf8q9fPT)fnV<``en10rAk9wzjt6_wVa(yc1y-CX}Sm z2>xZ45UuE-L(*-0S;m(PDJ}A8cx2@2-fx%UU%m)_6%uwyfK_iFI9JhCBlv}=RBIW~ z*kQwpNTk3yCJFw?LFztT1i!5M|Nn9c99rGC{?6|npZT}<>(kT}>$QZg>FE~7zPKwn z=PPa7Up;>O7$q8+%$t%BYZ(y{q4MzX+s~f|C&z&n78VySH2)ZR&I@yL$}Z#=9Bi0U zMR&7^6Mh`-t{ICkF5qytHfAr1sUzM7IVV`k%gG@h3m8_Km74>;fzDxDWpgnK?cm}< z_I7Xm^B;gh#-FH{p<}h){&91z?cnL9dqoA*ePd%XAty&P9C9uGKT}OH_&57@ZnCDP z2CDddri{~v+>0;|8=9IvWSx8Y;>C-mrab@x#hUE8`uYi_rS3^w7xne^;hi+DU%zf= z$LlJVmJowXMP(;vr|WBZd3!_qdxV`B8Y;uYG8ELPh7my-d}+P?G9_gUD(Fj-caC!4 zT_e)<^>yf4w|>nu=H=z33hU_>dw7g)0bD~`!Qt-$-0NJpK#@^YtN>_Y0$8lQXBvJU|NC1KzCW6Sx_`JM)?XTwRN*{Tb^Y|{4;|Y6 z_U+p!Vx;LwwapAbqtVY) zLXxhJml&z=2Utd@+Cw?i;wtAl}pKnmoPfL5%gbZP2qlguK^cjoW0JWwl-dBGDh84 z!@w0c)#y3p_?}jSMbZ-aEGm}etPSdc>deQF4969A9XzivUR26m=0jKCE;xFHlLXON zn^oDcyI=+rNR+VLaHXj~Xh_DMN3i89C6M#PD6UOaKl0|kv0i-t&vzak9vaT`7*rfO z6rmVQ=K*))sdC`?@OjxAL-svc=UfZOweLeiE@6J96}ay|S#gbxxU*!GjtpKD?yboT z&y#zR%m>+25B7B} zbGV<6&+q-4INIL9!4iPU0$<9^Bo;j}T?HiVu_Q?bLJ0n>WPQ;|AP{O7kmQet2hqZt zl6AhgQmnFP#24@Q4?hdi>yD=q&?6gm#6x6Teq5VpqK4E3~l; z=s`NQ@2{L3ROz4c&jRJ(U`VDHBK3hD>blgQ2sgmG{lP9kSc^)lDAGGentqr8!5ib_ zxlf zUAnuz|7S$Sj}K0lb%Wa&6y5vx?~Cu2MwbZ-olEQb`%-qeH;(Ob*5!wa>2WBtR-x+a zug^d3!LH|}(sC>M$CAH0`2Ie}qQp?GL5&&r=IiTw@$1mzd}dn+YXR;TB))!qsUmK> z%&a`_j$-fX+FB0B1+^$&;flk>9`e$aH8oMTgndH*9`}H0b?*0t;d_bGKvGS|xSTMC zFJTL%@>wv_87*sEf#777vQ6j&wDH(*_~iJX5d#<3Y_*-rmWIS=p%xu9=@xhBT9%k< zsBDUgwJmWDL=BmW??R_k$I-{C>z5=O@}*0fpp;NCwu|y439s+|%2o-vEMP~{=F4cl z^08mX$=y9Rly$G;!LGRC^NY7dC{5}e&^3DlM)n=h;@RP9HFoF96PW8U@_H|Mc=hj$89EMC0S*OZv(K<|r5D_b;E!S8m(Gl#JIwEU7gE z+4h-#!@P{I+x{cfg^$I0=g*%X`axi*voYI>Z?Dj2j8c&e9e37PY|s4w+AX!Mw1p!0Zg##-d^ zUoECAX}4u|{wyE=dC zc@~-I0B{2s9G~lMm{?dm(S@&g4K=CY=;#Q@ji}kf-Cajx%RI`LhMF3%;Z%u{;Dk6_ z!RY7LE5^pg78e&4D1c^-mYBxZ+EOR+-7-Vo!{yL9QWL zJ%|xECo8f_OG^hC(7^`5cWYk(+cuA~#*0G*`m z_0|zNFv#wJ5@%eRZT0ol9r{#|dpLVEz2T*NpL9X-feq<%SDq=Eo3i z?B|D3!uX;P=I@V0w6(Vv#}$Pc*BadO^;P8{EEPwENde#^So5B)S%ltIpsb5)(kGA` zZ*VseE9?fPutq;RI{EVL@A_|3g{5p-2;AqODR=;YWEr9-z2N@Qd1W*|=Ymi0?&<{4 zp_&#TJr{G6th}f$Jy*KVP%uDXZ<`=mwKhG?e$HiQ{WD`V9q;8{h5Ru_Q3KNoOZqnv zW=@WdK@T5t%6sgrRd)a^`#dpm2foqHkf!$e z@GV#H7=3leliD5XSW7&%wP#F`FRP(akaPWzo5_r{5SYeXYyzDzy`-_J>E@TFGXmz3 zxh604TZ4}u*i3nNWM^k%TYzcOWjSgAzeAMQ78)1p^7HpU(#A_|=H}*by2GRBzs$7< zvc~nYA?E}f;XGBWcmo`UfwVGJZ3hEIrVXIBeP;nBpKMeT72G+XpP2X3KB}q&WQMQ& z-Ax@BcOI!HTQQ<>2Vs4b%MRDtZ@go1{qY3Q@u$hj@Px<@sYLl)-zMsA=+!Eo0IDjc zWF93BHHHaq1yWOQDl{>pvppshIE8%!J-v5t--<~}Qcu3VwM;3XHYg)4jr1#ai@xI@ zL(7e^G6Z-9T||)ZKL6nf+`RrI?k_EVPQ5wZ{|LGyi^M(>ZRN7kHxD`Gzxj@^)I>;mdTXPHu(3tG$>6xGBeeXM-N!Kevu&3ouT^lEPH)h4M zh=RZDsKhafwdV1Bqvw++PcAS;uZYdXhz0ck(P#5?hQ`%Wh>oJP#y$u zAd0@9Kb5Z@Q}*Co-*3*m#IoJwez8>9t-!9%O-ezh0IYG}CMe+zaJ_n8I#LIiZ7^Yf z8=#IsJdfiwtH9+*h+i72|BPxmZC`| z+pQZtZa6xI??B_BzutY>Zg;D>y>cglehL)BV zdyPlEa71LtC9N6|)z}MTv1O>J{dBC?_Rv2^#!v=zb#;9`tl96cuSKI{NTQz%EfqUc z=%m9ZLwt^7me~8^({X2gI{*G37t7katK+r6#VjXu&oBrAb&U68`vv^uLh1sH8tnHx zbPMNX@SpEA-z*C?X-eZsO3IW_D7POUo%i5Y-N!+g!h$0tSAFg8y0vvN&^6>u8q(UzO%5xFD^bbGExn23(E^F;5rBy zEN85kq-dRf0I;8o&FU9Y-vM__(rUYGs^%-HrI^Ui3V+6o~t<7YU@2Du9KTW_8{kEZUrkF;LQO_#jJU}6BBIt4E18n{>2;B4T{wBK{r8~7tbpRYZ! zNah%p2YFb^e6p<5Q5sP(*63-0-G#ZB%%_yGGWC0m;6Zr29|t|AM;274wAGV$pX*&g z;fgH2Ab{CAXUJ9wWDEK;SVtFjX#N07(9{SF?PMvx<>a&kyrlC3=$MRedU*0rG!r>u zU5H4!p|sJWqg=F$y|Ij-SgDQ_u!v~PCQ_(61A+AgItJ)mtk9m5lT%bwG?#@74Gma> ze=kXgRF?VLx+Bbeg)?laC^b<~RQr?CbCBrHtD=*DC19o^!qegyh`W{m?>i&|N#To& z{D4UikwRAhGB!O-LZ-^^hjrZCJ??ZC=hHz&?I(ts(Q^; zT3;x|B2(|=TbhU{C>-qnAin41Ujz@aZrD@LD9t-;m9N3|5g2c4(};B zVhKjheVG2$5BfoIKLD>qMMcG?@LNY(-piM+Zf=%%}J{e_BIXtnDaWvml{jI0m)X6p#^xDu|1y!J|7go)pl$h?^a1mJ_X9{#rrlTf?SQsT+(yO%9V5 zEWrsJueNIjLJ7vHs#`2+VBDzzYms1as_F*AA(1+zxe%C^>*@qMi31$QKtE7eiI#?CdO9i_zLPL*=2}03KeydZl}? zpI<=o6p#CQ1_!lsW1vJGacbW7_I%M{B>mCHaXYL^fqT?35qzxf;L8AL!rnrEByPK> zv)#$(Ia!e+>)zMbx4h65VJzaVq^zu!z!sKq=_5#O05VkWuCuifk73DTw?QWbc&?k( zyScY_bG&2>9N*$LO1y^x4j#a}Q5e((-7qWl?$p)-8T5f4iG$_%(%>nO%H!QBJHTED zIyx<8|7fJ8T*Ono3~^E)fcvfRV-$>;<`M=;>!u3e)Id|qtQyNanA<)#``GO~5|^`V zy;Y1FU>{5qrK9bD$pHU6gbSsrstQUII3k8r`A%$i@7^t`1c$oTu@7usvX}AR4vV!; z0|MgJVD`SkyT>3()ZJM#H`3Eq?8M674l6#(a)OKS!B}H?^LbFzlSrD6WUu=A=0I|X zJ_<5zqw`ya8#V4OF44(Incr|4c`paG*ms44HQ4jB;NGc z`uhIqCDy?En+t>mUj9^CORD=mnQ1$bj_(sx!vmP#*`mn!Gf@XDqdjn7YEjYKe1`c3 z1nL=}eS^>}@TJyC%W@kihBgTQykX)*`bHV&Tt9$rtm;ca9>I{nm{N`8b*6Cw!xd|V zcnH)7<^t;INfw7smIkJkRnR>nKeHzocPz zA$7U9lG_7G+#o9eKZq#lezW~!MCv0^12r`@hKRDiN+X}(7SrM?YhDPZnyoV3!bF|r z-Cd#ZhzL*(f4t8jzo(uaz%B2=L`yjr`DSaT+(IgySV&rWOPYxrU26Gn;ND}zg9i_E zO^DZN^OYH3$QvH9ate%V#27~>|EBB9ZdENJi+T0>wbPA6g>n!)@UkhKr^&Cz@eM@v zoqk}(3my6DlRQIYElL@s3ar>Q@)l``qGIAbC(0&(tHCss_nNMOBa8K}J+DoKq5VyoFcGvsZle=4Ibh> zGWpNOO#rPxgVD|kIBVO6_##r>CMA9T{0ESv*DnTaF62K~^NlNm1_dJ|FK95C-?(oy zfAnmbFD88d@|$oXQc4hM!RfRcDlg?f3!v;uLD+fzd5Sps+k|P8Ro7qIwAO8G@K{va z`1$$iuo(ms;x1Q&n|P6N0v8qt+f!BQyz&fs>@GMt&^^>R)aC)>$?#xLJRip7aq^cX zb04aS;tK0QMPin;#Ith%UxSVTtY>YIKjnbu^I+Zp^8q9mR?U(#;y<0-6%t5X`KGlao=lDmfU>*UMgFFa5+L8U4pcbvG ztE*}3d|#Xk5s1v2o4!K2k0o2d$A)99s*gQ8-yV1Y@;hEtp_v`F+w$8%`m#g$W)?(j9-_wUi6 z^xL{y9BM(EPc1Pam!_{9#~;HO1hjY?)H{Ll8QDeiw>xPZslTOnpBCN#x%2zTbGvF= zWtgnLsr#~J*#*o)^#qf*#ZqV=(L=E27yvj8x1#CLDPsNWYm45m0xTjz+<5>13VX8` z@h$(|DkZZxxKB}0QNQLpI>4JkSe-i{R|6DCfk2>)ix6{P6n=ry!Q3H{Rz`7fvdQkL zxYfrR8jJvh5wX7m%^Y;2lbxTR0A;JisStS=7-;y|Bsk8r$_H8?m3^E~+74{4%@507__E(Jy=C;TV)w|MS< zd*<(iib#x+k#T8xx%k-o6#EQ)##~+f96pNn?0+C8;39vko{}9NuCBT(Thw}t#aX6# zeSUR!Sw%!!dol*T!NS5q$fJ!_-{wTXP^LSLC^?BA`z>wTqUGnG7Bir=C!8fb*yp!uNxuqH~g?0GG&0@qNsq;jXNM9ficHS1xvN!P#f zXSG0XgF&+u62c6LCN?!SB_$b@8W$G@y$}@_U)^1sg5x$dx-f1~$H(M36jW)sRKdac zw?m=QqrV+~#Z0LI0yhi>p36bl_QIH=vy5e+8}_cj4J`ghLPARV{<+Hh$OxUXASXBX z^$JUwh7nZt_10TA7COVgAE`FX4>#SR$-Q*%Ca5MLt5jIl7MK=wN08Gs-TbQPHkv1H zR;H~H8RUwZOg|8>#rgMZiq)}uwr869ZwOakIsb-3k;6{d+>$PA73u-T@=#yl%a>2m ztsOL}N5;ms06CAwm}V33@;--L69{cD!Cp3$9)!pUVEwVmaoiu|lgxkJyTi zj#Kz8ukQYeQ)d*O?}qf)le5=nV2-Gq9+`+66UN*J2w(~@9xZ<8-pMHlzBnY&(zk`$ z7ts0!IL8UMsH$htQxy!C`DF&4k(Ig!KW(kiyzA==vRT247cM~2JjYAIUU$R!934eg zQ-CNTec?ikum&od+He|DhVWO$4BmudfTpi=p`6HV=aI@uvaE*(2rq1*lIEleNPQJR zNg>CFjix(U&`sJN?Bb1c;}cgRpMF3^uUcDm?x!XhR@BkEPLx;*62HG-5+6dH;L))A zOLQKUSj{9M@EWT5nk7?(aq%s<+Y47p1+FzTHkvdDX|l4gh}^gwffn0+s^(k?R^E+o zWP}R1I^f?MpX;^64~GHhDBfMq1c8T1^jQV@90Rb;Tkmc0GxbxrfN?$|&b;?%UT^Cv zfn#E#oQl3ger2g`z?h!YDT?N4_2qWZW)U)FfOLwqjRHpJ=1zbRLe8r$fUr3${csp= zJ%lxvaX5iGXm9V*L?1ObHxC*>8p1FiEg~WUSbP9&%S8ED%FedQ>l%WJa2HUJ;m3}U z(-c9iI4dMKH=Kl<{#d+&Zy2>ee^Vo7lW)(mSeMz(&TfnTv73nRy?at7FA^Y3^wCY) zW4wfG3-oGybhF_gyhZHHd6umfwpfO%S4aN*7nQ zYFxh-7sIe`0GTN?fKMDNj-;oTasOBZ!G!Frti?_`Zbc!p-g7R)Y+m7Nch@F=0dvu| ze^el@q{PJuUzwE~Bn%yP6DQivYkj+*l(8G=feq*zT=N%NyAvxkJJ2Lq&X9uCqh^{! zk868+@wO(D$hq&(!*R*A8}8#J^b9~lnU;kf=2>=dSnf7kxjcWIJ~?4N&}}z6p}n%= zzCV0+>nva0{`M1(I7^ye4Y}46s3rUa`91d#s{Us1>~w)zw%Q0VgOxvkFyS~w`Ws?j zGtFJAbB=YZhwB2~a-7yJK-Ld{tAQE;)3eq~s~$v*e69gSu-zaqx3Cu-&TrGgunep* zq73&SmYiQay|S<{Lhf!e3}pani>Tb(T;FEmY4Yn}AoUIntpY)Jl<);C4vl^atXUzU zBA5%<&kuuw__+1YvylazY5XUZnVjc@`i*A_DcLhngb`KLG&FtWp#`J;{bW&WWJy$A z>pKGEXl1<(Ep{+Dpzkd8^_LvaQEdhp6$Q6}ou4)rylTyin@^x`DKdSJDiCL>v~=T3ah~mN4kvw?X=ZBDe+$i_qep$vBbS#V6MQ zlY>ZMDyO;E8Ac3%;m_Z{5Eed$5Lup2x+o~c(Kltb<$Q)#=f5f5)m9`a23zk zv`D{-ItwN;=af+$A1XpKWprw4pjO!SD)qke5+V|qNdg}e-)5GyLwE^VH73~={Auhi z(mNIn0b$8+Ms>Lhv&&;Cz|yWz`OW9W z#hqzcxq(T*9qbqQykL3A%eH&wDuHR&SKQhf6}M`IWw}5kMybHY>IM6({bp(Ufm92p zUyl>3fB^CIrT)-H(aC=V6p>1RxYe1_LdCSEVYG{+ZKpFs?Tiq{wbJ z-QB$6vMBluF=FM4Q5iHFuSej~zF+IDWsF|#g&n>b#Awc~M=OMX1c-h>aXRV*Z-3M2 zUBGT>RIYZ8GH{mlXRO!-O=HEWISZM85KeV}{d%3Gi7!hgjXH9QFR`=IHrQzQe5=ca z(S$f7&pNYbq6C(Ia4*{Zdiuw<_-`^qCW7b}Gno3sRGj%S6R_@xM|=v)7aZu%IyA;F zi{dU5lsZHuq3F!8^?V{@`pg0BV`<8hv|k_9X50PgL* za4Xdj7lhJJcP({`r3jz&KF23@5_Tc~7zqejIxO8d+1a7m*C7Rhy;2nC9F}Tqv$aU3 zC--r-(UX;B0eZa^Y)7Umj{vCJzJ%0N1D?kmTkjM}|$ny2o?)v9*wLu1yv-ms)h!X=0 zbzpKagP3PtnH?~v==y=n?~?%L7euQe3!WtRxEThYP4i7|f!AQEj8|O`gt)N)pGHFT zs5{8gEBWAzx$(_THF;ah{ab!wXNZ*(kjKmpJ%R)q=t7Rav8X=BgZQ_3K=2{+jw?K; z2F3IeQn`Tq@Ie|GY&BA6LL5@iR6nVZf3h<({Wm0!2WbK+sHuBBIDP`*&TE4-J8*sU ze(YNU1|~DM%GE#Sp|`e3rS!E6H51XDNrj!-(mG5iLsbM7&ndWEF!Z!-8e<B{^H{48E=y4u%u)^ zL|<%`Kz4wtXLvE}xUBvZ^s(vu?j8=EYrGb-KNr0)BIlF3o^^?x9pL2oO3JJEM) z@lAIsi(}uJGoX_iU@V4;6%~7IxH&jhS5~k+BY@ywpqZ+u zs#}aIqhG?N#$(BA#_T)!NMg-o(nL5<%&&}s$*C?ViBPnVX=;vSG9(-B#kSj9r!QDy zVmqg%{(>2#pqW7URk!$*)H6h%b4Z0Wv`~GPCPus-7$yiA3NJ{Ea0?5eDFE>mL5nUy z>m^W%9sx9yezKj0%H@jurJyJ957$REtPZ_kpj4G^g=XMv8Til;M2s_uGl{Wp0{Iws ziI}M6H?%i}=4p}`z7Nck0sukEjS8dPBL2iu)B$1`iutL47c+1<1VqDy3e9Zd1}zHZ zVoB`w64Z1ihzDtLgMaaykD6WrN&nn~DUGS6*za|4zECQ^oOzzNFUg={l+YY}F# zf;8MByCFqXL_(x*C9mFD^cp0Mz`#K8Ga?&6rMLt^JrI2iOdSRYvl2^8c|M_`VIJrA1Cgc6e}b zaCnH57c=BUB9Tl?Lf)E)w{H83W#1CW%qEk@7711k-G zpkaU&p{=2lXNdA&Vh8WhG(8kn)gUDa+Yhl`kdlThHK^%H{*a~s{bCL{hmi*2;4WNp z9oJf193P^wx%uAh+wY!UYA!AoE)ts%R=s{*Sx%1WsU47NpqIw(L~uYMWe zb&ieEKknr#4kAMC-TnCyyAR%TDiY_>Po9PTJ7)CZLEzYhMNep;1m0 zSpb_lu=7E-eA^QJksMS${MQF9AM|YjbB)RO?|%V;#9m0VvkZDyR|vow)O=L|S=l;> z$HAuqFGt-O?Cf9ol;L@T*Pt+}fkMY2=Qaw`I{fz7?QhKtxr%qn0Ce729q;;%9ni>s z{rVq14$MQ`*;J66TtNsbDssymj=G$@JVdc?gHs7G0c{O%3(t78JslkdZkUEH^hyoY zcuGlC6NB1&>8{u2LKg%dvLG7+x;myFWP*Yk_$)vKW30b4xJN7&w=ieQxqpPL8vi6@ znIPDA0G|`p+XN%S0>uOjuCA``5UdC25#lq9sm!WE=u~ov6?jMOtOiJ3-GZ+pmL``x z4w^%)RuLRT8g(_xnydEqd2Y!l#4UW7?FK{7)KoKcIlS)OLDF+4hF8N3^f`$2Mv}}X zo-aFI|I%Cy+bNL0(guJUk5&U`8>Vv{X4`r(nf^iiEiR7s22HW#jwz?-23`LqVXUrt0cG zKim1n%wi=I1*=*ERG7+PMYPjdR+Z zOa4E9U4D5vKIjT*2B$mK)zx+|O`yeHv$PaC&F=`1%@HtcNj&xwK|fud$qUVw$P7&)5ZSo2tL8pOld;vJ1e7$ z@c$^Sqd*YfpeLTStg-J;V22h3k~ijPGSZn;?#FPJps)@DKER(JC)}ETl@Wpd`t>VR zzQl4G8Oyoz=g&GDX=_>h00kb-TU}#gqaFvGK&T)fCY^T1tQ4Uqu-5>pii3xTIP*iY zyHz0d04*FPBh#{K71HvLMf=CI)q_|reffhrIGRAY$?kMlYQ(c#36^lqk@t*dJA!)w zC=3cqOC|H%@GxF;itYtkAT5M#g!sSXZFC(0UhQ@s(Av@X?ynh}f!vPhcCGk{5m#w}y#hkiq$#XYu`@D8jLLgQ_Ng~BY^QZ<5sERg zosQV)Wi=3$Z$Ix4`6e3npD|WQ@PBTMflUHRsKHPXGi-~2DJ3Bx0V}|7Ul~@2F>-Oi zqRdygR#+^ebVXupd>pcSg)uQ&0N;w?+rrqr!j8T3-!?oBDh4F;|C&FM$rV)KxieBm zp>^reb&CIE2P^x2Y+_j=w*Czvtsl-~7Caq9P#WT-e?NGid)1whD4AoFVxMX8tiq z5Aj~ne_;MVoKDiRCJ(eT<1tt1x1=N_Zf?DO61YG5HY&@>>G1`--H zI21U-z?<>?c%oR4n2fq}&fCkY=qLn&-jdPe2XX6QcEcXd*^gO-x!$l9+opS;89J?L zt)m7AehD4WcRG%?sU#&QinC$+7N)vInJLB7&ae34!xVq3jUBWtOpdeV2E* zwWP)N16m|jof8N@w5HkTIl^ z`zw%~U><2_k?|7^a!rji6eh4F@VlAUh)Wfj`AoHDW8Q89T5ZuxUI!5YB;I)OT;#fi z+`Gu46ItQYxj_!YiY%&%aG$w``~N0s#!wY;qq12s1iA?c2{24yv?oBU2$XM-zRJP6 zL&09?05BS4LtI6h{Y@m*G$uAKuG@KYbwUCbtxH$V;TL^sqBVBz9rKVHOo4z4M7O9d zNcA)B{skfIr+6&B_ZEoDL;eIk1fI?$?7;~GMzFZd{FX!Le|{noxZl&~-3&XROXBCE zj9x+chiV+`AOYO7YQFh2-Gb1sRvXrckHJb4pz#eH$(DZydx>B>0;O^X1O}nQfbDOw zrc87q5ul=isO$}ZLmQ-H9<*9$5NuMz_60mOK+WSN8u%v6s%$y;I`C6rU3w4#p!E<1 zHdl5gX@WnMCc9;?#r6eGLyD%VE3hMsm)ANR_gW5(NChB^y-Qo(6MTl$oWh$eC^f@` zFb8S{=k)mZq#y%`mO;{l9~P;pYC)8g>(|a(8Xvw(@E+Mi84K`n3GD3x8(+wHf}g0J zHNq!JLrIw`OM(2SAswHxFuvH%KSY|WaCVC~i`30v!kN4- zz(9VXZh{&8^SRSh0-ig0>8u-fOAL!(@mSov0^6g;@p=O?QX3A-h3&A$obS<#r8i7a zaB{1Gl!QJ*9sn6fQcBOl5N#y|1-3xesxDoY@_==OM?@YRx>zduDfgo+F^j6)O<6}B z1yUyy+TT=2D)6n5$U`v@Nk4)JbATHoA5N9_Er*qt53W#*>5LKL(8d)07%GWKCs2BF zz>^G&NX;C!!v0db=(c!)!(G5-f%M@=LE$YcSmxe`U8fdgJ*$gfC?md#$Jo-4Z5KiL z*S52;V6`CMs@s;4c~xdq*bgp>?|J~|f>L>~ZP|mYg&Tw1PRQRSu=_>xE=0(8%p}Oi znclZRXPYnFvfd>zT}J}AO29wetfH;$?eLp5T1AUuvx~ljj*KW(Iqh_~v(#J)hC@PR zrr3XKL%mBsf5zTx0>7lrbyf(*C5!vQ@83S|?i4Bf-mb1OoIK5mCEsDsZbXU{Eg*@J ztdq{m$Y3stg~uIK2qEd?#Y6vQY@?+0u4GE96%o!UDV#&T8Z5IFN#JF?I5#X1k7@y= zGUT=WJF$Wgh71-Pjg(#Z9(Iy?SR%B$KxUqJ-%k&o;vp!F5B514ieJfwwFEUwU;!-G zBJB~ueY!O;(Ga(N_2R{E)I=+b@IG32A7e4w4CVrZa55h)d!Wwh*Z^qnHQDCJk>B7! z5MB1^`NHQT1>qS5XX_9=4i|)^%lY5*gywsVU9_dYpmuhPW=TLEHaVeAuR9ao4DP+D zokyu?ax6#(gG^dhR(FS9nwC6W^40p)K21*;-LQw5K4 z6j${iE`eV=Mn9$B9IKOm-H6oJ0HR^Zowd_5Gas_$_0l7_F5X($jwP413;h(o8@9dX zk567!B#P-e zlkoIMS_cVi3*(^JX%F@0AmSZ!)g|ZXPYPMcban#hJ5(S8ryNgAGL7NNgP@?ta{^_d zJ0GQQC6Jp(oh-~lcTtN%5F3$RZ*m)R1OZRm>0UBZcArnWLJjBRO$AZJ`M!HUM_U23 zmoP6}=bsZN_$Q9vv$(V=qDd>ZWUs+YYqPr~W%C|lO1cB=A@a4lNb>k|kHL!^)3^8X z;$mTmCXq|rVYLhDw>IrvfQ@_9+s9{e(hq|AQ?sS&h42}iX5P6q@Zwtj>R-8%Bj--E zn=Wo9J~k?{o5ve@-m+cyU_{lU>|RuvRPXydeJ6H={Fgpru?4dOvrQOW`1JjnU9TF2 zN}>)Am(=5A>;vDyuC6XiZawB$!gAaPi!w~8db07Bs(`WX71$Sd!d`&(4UfM$fEK^E zI-v*X4kxBSq!mV?&LwEm1U=SNEqy4!hCoe-ql(h^vKcEmLcFipPz^?ZUvu;+JS8Rl zrAu9}zZvi=U}*8{td#2JHiaPj0Ihd}cj<&x8IN1g&eS{6fJw9eE>29OY zUO{upBWZ^8Fjkq~AnvAvbHzUL5@<-vkpBca*>`9f8Hdw)jD3)2npqApY*gZ>tcrFYR3GHYOk$;P*g-xYy27!RfYXYip}R!{`H$v3(_14nQY@Qp7)4#_BwT zH$~7W8cxT$ELf(QCI2L>n3aD!7nN(BOWFGf0tmeZfPMwc210W7Atix)o}r_0x(q@a z8{1hH2GGy?jfu5lmWj%0w3a_P6DG0p5!x+r%K|tfnNhf?a5li4%f0)PECj>3X28mo z0(GUl6>b^6g70{Ez>wSc^pdXXa@9|mLG-`lwb&J$R_=C3QYa8lQ+?0`l^4wLzE7N? z?WmDh4-TvhHkE0f^BOS_`mkp1xvpf5uQPWjS;Z3u@eL(_HJm#(?3NtpS%gH$(s&`& zX8fa;HfUy7Va-0=4Ymy{Dl2P)S!we4D+CskfX%CYZuyMN$F@*MNA)cb^=6EctP?XA zsGB-;Kq!D69^`W*4*&-GW&gbw(Q*Z;pzDiw^psFKs!t3=A(7`zgD1nD;`Ufxk+@Y* z`}&TkbFmGBTkdClbGC)Ps}B3r?{7gdTLwYbIBcWhcXckW!$x{VR#uj=jD&ebCwnKN z7NvO?j17qY^|GtJg+pCSU~wWA3hWY!oY&J^vUZGaI1?5GFoNa@jKrNA3peyH8VbD- z(04Iz{IBN=$k2liZ!;vC2XY;N`{;Dwhpiw-qISX7$Jumc*eTidhsA4UDcs3KZ{7@R zp?|qF>U$l-ciQ)sWSVkS%OaE8Pu?#K3R}3$-j(Dxo5~02@6!J9NVAK(j^SbPu&BL7 z+eI=9dQ5_eDdEIBXLwS~tNqTou?*Sdt0(O#@(a8UI_$j(SiX-3k7@`c4Qey?X%kJ; zNdj^>*x2|MQnyuTG6|5M;CUMy_2H)Jt!mh*JT>CTf}tP8S(N&@*cKUPh}4*a3i}pS zn;MDVk~7n>j0xV~0}BN(*ldPeWl`p>3xx#Rg0&Eeswn)@AJGefZjINI zRoM%f#1$14pz0MLs-CN-Kc#AHk+MkfiKO=&8f2)2*v{vf8Cl0;&4Pjg7_hc&0WfQf zinLQeH`Qg%;)$F9wGWoD_xpW5JhTAbn0Ris#z9@|>PJe*>Zz%z0Y5t0`-gwx*?)!a zA+LpEqEyM~ph$JebD4Nvh(yI{`u{5LO{1yo!?;mIZSyvd$vhK6G?`_n%u_{%22-LGmCVC7 z6@@gj4IyQSQYrJ021Ark8B#)~R7kwP`+3g$u4k=tKAiL6J!_qHTF<9e_1O2m|JVPz ze$(X*nyFR(zbZJ$aWT;j0KaJR$^DP}AV8Ko0q6kk#uqPN?vfY({56W>&T^xZhT4PN zNB1Jm$LgnMoK{d%>w5qGJxILg&!4xn$cTC&sP1WX_24J8y5>SMYx@9Q4+8eEUU?$c znDAV;jcd-Po}zK+nY?@QeeWu2s8^1V0kehH_p3C&Yi zSJyW%0PyH~^ypvk=caj#;WuDfuY!gh|Ct(nip$K*L>Qik_VHYZrr;#e*^uNoYUo&5 z5woLssXZs$?%lcWiV+cp+?xdj1$b}vKRxpSu``JOnGt=v0E&bx>%v7hMAZWyk(%<@ zgl;~?T}YpzXj$x~$03xcvBD>oTUuo2G;)M1!LRbi#IqmUnVL4e?T#8-!0e6Ua2JXg zBolQ=x$72} z`zzrpv-*j76u{`p%AaYl0N)^=*_&`&p9FQ)O_Qd^wc6sm>B<@V_|%eZysPbAIF9wR zD1>yVX8p`~=-ub|`1YX>$vsl5Hx69*Fx^wp#d)WQ&T2$MUY@B0>O3g! zsD`JI3`42C55iJB(GXrOqJYff>Hve`<>e(;)Q6@36;xHS133i18QpC+=!CK`GOBLg z+@da8L=xkkaLCEdK7VqtW%U1d`ad@UC|v%*f%u4yW=OxMpQ zGp_>d64z&8XGdAAMiuZB-3a|TF&*RDJPU6EtZ0TMm~P=j?k#-sD~GMFzK z4&(a+mo+i)Xw!omDsFW3-t>Lc1Tp~K7_F1uKHdAio|!V5xio9E-k5K(MK!Bu^EO&b zc+X)zXDx*G7&t#5v^?~nj&q}XqX<3kLFaj4=u^U zO9G&<^udE?ob0hne{a%p%{Azi<)>;zco#kVxbkZzr}hTh%7@WlIvRjrt`Bh=_Q557 z_NqokfKqmMJq6_Mj{@)zyVnTxsepRFE3UAz{o7wOehiD4eNKqo?#078>o` z33<3fQscXKn?*%kakolKOShgmeOj#bZ}Tx}^2PMQS1G54HHto-`xHkTH&8Rn7|xkl zf#De*xsyS^!(GZ$EM}kBTZ-5Z%V|d0b#gLBXXxf~ut7!=fcZ##VtP6ZN*T$Dii)M* z(>4A>`ruj)qEi9SU6f=xWJBML_T516qI3B{#&X8HyIU=oo0fL#<|p*_X7)Nd6*G~4 zLxc8@D*nA%0zbpB{lWy)dNa<{tZ`%?0#$BnpWtHXGqUtLbSST+RWO`^8| z(u>Mw6|;VEVS%)pYwk_`t&03zf-9sKe7QpxN(UcTJ$keqCF3S!?QWxN&JVnM%-UWs z{Zgh{74SY811BcCu1)?BUG3cvMcvTj#C=UJquYO#n{T10_0OZ~B857*0s0;sJ&TAH zhziV%qEjSY52l`ucsINvtXq_-c_|(vGFtDHMBx$<0P@?AnWD0xVh)spE+Ad{0Mzf z)8$R&a8Z7^C+_vK!(PVcO9ZMfAh7uPIhVnQV@%pPJljsaCLeba( zpiAzSG&nyn6ms_Zp=%eCG;UCmlG3d4kFfnG>J4K5w${0%pMW>fJgycUDUOJU__xZW z&Y9-G!IzU`Q)DYAw`vgK(cRts5-fL`%;y$D8ZpmzM?afHA_ZdScgV@;EaOfMyt5A5r|01(dhHeW?&ehT=3AFp&I)1&SdIB;`NQJV&+lBLJzBqjW zWbYRiWa-FRnApR&wO=#~;bnvh1SAEf#>wyBF<5*XAHV9Zdv%-RbWQ^_P9~>$aFhPT z3u|Fvk&8A{8*(h5S>leG8X6!TKjV=bZmL5GSi_N1gaQminlFZbA6O2{N+^sL&f%hy zttcx4Fz5tE5WIw@XNM>_0&ds`dhx5jpYXAFOaRlj!mv044R7;FZuZ!O0C5&PinF-F zIcm&#yuDGa5k}WVpkfG&4D5&bYjlJo$qhwc%Q)?_f&;YbN2u3RSR(M*;K=!gILDMw zJY@j*rNIyH-~ZG6iBDI(BN@6zLfb>+Mo|LsiLT$c0q1Y%Scg~`xRHGi>(A+@fh}EQ z`4#MyRm@tU&3@=FxZ9AW2dTta&7q;gX)K~a^puO=AA?&x|DNEj1#VAe@d zJMRe}nXJ01YQ)i08oZE1dM%nw?`Vt9;`{Z!D&Z;jY=MTDide(0X_6MbAB`%m1hj$J-V4gQb z@)%wc!c3|;nQ((2`yzwnhn*vKQhyVoWny!8ePj6%bHp0mWPF?XictgXZec{^y z_UFC~)1$0&b7M#jw6*2t{Yl+#3CM3YjpkZhOpKWPK$*w7Nc-NmFm#}|qzY(Ol$UdQ z(aeh3JLBX*bAtpEwd0>n)XD1@?ubV?bXupQFc}*fhNy;vxQ;cK@hZy*{&HVT zayc9vu55(n(lg&~FmP(v6RI zb5AM^xg}&ew1tSo(%dH5QHh8~kDXdf(Y>VW0dz(}`fBt9Y@nfL|9-Pyuc&6U77}@H z=!AtvUwWP>XLPg@{!FwB_^j40F+@0^?|W;*#+fvL{jz-9&Mm2fvQWZp{lR{Ld;k(JQl?p)ykILpO*mY1&o**>Ol)i@tR`(OBhbQa5dM_W8--SAvCUDM{f(iD>ibt0+uO9U76bKs?x&sVQYUN#=}(c;$QPpI=-) zhC_{uaseVkGJk@;371)AX(>O@1X;b{U?OsgZ(~AnVj?q(hHEWNE;BTV$dqV|GK+|f zj;36w=8OzQe2QF`o0}WDD?|O>4Q33bAt9q-;ksK-j?cq+<`%Kh40UPq(LU&JBvW_C z;ww&wM%u}FlhaAj2T9ALc5S(<7JJ=z4ZZIj+rRtOF1LzwX z4pkqnu6g^1e$HC!FV%s+EnUl}q`HAcNRy@9;JS|=G^?jiG(H&P+Me^aukQ@%jckjY z(JJw#Ie>d4_v5RX$62NflrblfoqFwUd6f4P4+s2mV(S)d?MlB7eXn0DqiSGisZVqD z_D+~ZtdTFYY9@(?qjPmI)aTYX;)zxv0y6O1$jGH-4Sl7Iet&W|!dY4O6YWItZ?}7Z z(Go3I(}@tk*GA!h$_-#=Aw1+0)*p_b9gCKqya$DO+cCjfKMEIq0)=v#t&!iz*Xuu6 zYM)97cUWwtdlP%>)&aE2D&Y`a)y<9Vwxu;}OHrFoUJH?owb$~60|<|0Pfk!goCbC% zep&>4zq=~mfc zmqrVnkri=WGttoHQ+McImS6W|B;;;^bQx*ZS27C_1IY&}6SF1TtXf2_;8Y*YSV=gf_~prvKAC$%;aB!Bjndo9F1! zqmW#@c8gw?v@HHcf7h(Fea-*TP91hZniQI#Wotc1v-X-@35S z*Og>U$L-g(e_&VxIAYxy^aVF6y7w?laQH;I)b>G6@byJ^T0Tf)#Dw#v7&>W4xonj- z@7c2l;4HL(A-JIO#OFK+CC-*a9tX@k?=YkzdvIW&8nTL%eA~-313$#X#L(9~hV2W` zac&opll{=MvmQNCM~4cUQ^*^?p$p48QD~G;%T)`96^4ca*tdAj9r5>qTW<-9?8EIh zo#9SH2L?;=KFmV;Ywz9syk`;6_vc@r3}v@_fnJktu{y}YvbZs zkQF^aJMk2hXIuIm45R>n?*R(|8g3@ZI<*J~1}ZKK$GvVN_u3l#Kw+zYH&e3#A$&ld z)b9o$&UxM!>9vjl!>%`tnhN*`1NwF}P`Kws^E37?7o(RMLTG-&S-ZANtNMiT*nC*i zUe+Dza!V*w^}!oLkZAq1C}gy53(g+X=Hr7-tP zv0t^}9R!DQYy8AZOe#c-jAqM{gIyKy7=jT(uZF*sZ@Z~B0f@*K^qC(YpS-hhvt$sw z0HK``gA9UbN2p|0h=?hpzi=UQ#3Dt}I1QZ0L43}!We}U%K~vXdqkoINMZdY(x0fvb zBy9DWtOe!LC9cCH`gGtek<+=>8kJCVx(5?60Ghsz_)YS)u-TFGsF#=o%MqAD87?a+ znG43`iw^wm<0oJyhEfnrxCs{=%0IQSsVNW5detc^ZEfL1U!}sH{ML`i6+0rdP7C*h zT~JsZO8cMrZ&8W3@7beYB7IEo*ACd5guC&sS@vUfz{?iv8&?IHRu@Jtq@<9q_oKsC za(>Jm<5`W-`Fb)qZvvglh(wLc4y(An?Q}U(oMb~_V@C@F$3Jq`D9q6fb^1GPhjP*8 zE5h{Qu;LSKmp6m<(LXLeKHem!0W&b0pt{My zI=t6JoY__)9cih7Rjv&$F4zKBEslv#f*Rhsc2r>{V%|cULoktVL%ssVMmUf7E<{C^ z!*(Su?RN-LF~}k5^>QOhO#1>y)b$Sdo<|jZD(o7ec`s&UBzo7Oh)+LcOv6yb-gj73?XZ_pp@ubg+8#QtFrF~c)0 zFve=HeaFY>;o&i#g;{Z_I1Js8*5cTPqbddsnN;EJ9#1bgdz^wq-~KK#munJyq8DlQ zwrpWM76ri>x1yt^ul@@A9w}3i>fHf}W>+dA$!j;&q?rmfGF#I8qNrCBnWNB?;yq-V zJ^zmwcW4KaoT|=pTo`;y7AQ8*sg`}e)ZE>w!2xusOhodR-;R19gVjX!yXd{EF_O%tj zdr1q4prpSuOwykR2SwXx4cB}U6QkR1MIzrD_7kfuyiHOA<=q`iV zMU!VrQGO}&Bi9;f3&EJTe)zE7>!n4P?Z|;go}2# zwB@oT?TlWf>*-0p_$yyCjCL@z#$6MM2zoCu9Rp+iy#`w0+ zvY)`1rQRwG_rQr7C)!2l8^g##tJanIQspm14lcG4z_hX~uggmMcai53i*Ubs*tXdu zP(K@>-vS;j~R%nh^=~ns$@%17i=M7pb+Q&8#V&jO- z3X`HAODN*A5AF%HKMea#+76vA*JdGo8BbDYoV7vk`eB=~C%d*)wOicS5+fvHM~Fcr zwazUC4m>0|8=Z~TCT^?>N^?~N;0|UdQ4vEPziv$XVsD$Dk#(r7zZ4$SCwoJ%+eO2q zyDW8RC9!BXO?j_QY%#w3+ngSwey#^?Qwm)@zlniF@&TM_>mkm)XHFr2utU;jptj3IE6Nsf`0y+tq(1GlJm^?wSqTKYOO zrweKHY^)@alTynXzWFz%^s>ijELzmKZkCspnykD@w=L2;?ND1(m6P`AY}5JDJuNr# z={qMdavLh~7U8CAy66umg5qN3?(3T*C06g!=_uJ4RS!8E;$}%NR#C0Q2K8MF#tnNa zkdNc%C3lH4UPLrd$GwpPd~nDgr7_mh{O1D)ru7B+97%UV^XbWF!g}HyAWdoMsrwwQ zK5Q>c8j#<^pu2Z5CIXRs`o&t_#hk|Hh2@yaeyDVYXFfSn?<#+T(U8Wq3D-8CLgq@5=AZ0IWG zON1#-9PWYQKwe|Gdu6A$yoEF*N7)zR zvJP=gim#Je>8^i|wxRp%10~w;a9MgsV^=nx-cqqHtvhHKi;f&a3Mb_~*GjPBX|m18 zRe7DMbz05c(Rr5gdqX++xTNB1zL^?F2<7X?Bs@BPMZ%NN$(Olj-&o#Q(y=yl*N!js z+i6$}KdL~^Z^W;GP8TnRdpeQ6qj;wu(Tj+I0qlT`HP9@`tbU!Ty5_G(%pVBZ(a6P@ zm=gIoDC#P<7`@0PL$9OXWw9r5J^Txn+RbH>4bF1xG181sw4??qhiBq)oy8sFE=lU@ zWVC+e*ftxfUEm6QdzJ0doNc1hU0C5x>{*B!Orh!zXF>7OdO&`XgF2bR7zU zt}D9g^7Z@l{sj)t+tB+zP<;Y)9K4&z2;W%_i3mu_Zhip71|Vv1fBYw4O4UyLJu3`# z91R95M)tZcTdEie*TiYr+<9$X^np$??H!#B$N4EDtNby&1MSGe%F3pzYt}S;TN>8S z+83aqhrs!ONjw`)8Lnxv)gJn*l>NDqrgZe8l#Qj5m*>3bX0wn2t|$keKoFbXfnf=eYem{U<#-7{P(ID0YO^dkmk~m7LD$%@6 zeE-h+b9uI9!*CA!fR1d$5lopixVE4&qZ}0@@Dx37OnPIyn{WGX*iv^?v1~3AY@%Y)+>12I<%!@(ZK(l{&c{?g8hh)p17aGsIAyb7bK3R+C^S^ zkF;2Nzp4ftG9q**vU}0rAKq$5mqfc`9P-t*ay*5FLrGv4*GRNz z1w2-{K{4S{ZW!{2<+71mOT=0ZQ8A~9M8(;qTf_nIO?c&I$1(}gd!~?zp8UuZq};Id zDa6s14bP~4m1DR3xp%P5{Y*n+JTq;S#Wl^{+nAeO{TGUEGrSW#MpL~#rCIwMxHHYV z`NC~whK#HBx^23qq^W<@50iW^j?(kVxk#9>s+o=77rppX>-c9pP$K?|Dv&1t z{k9|tNQ}N|gvJeux&Ot1Kzks=%@mUcSLFW;-qHBM2`vZ@56=~*4i=Mt#&Y~A0jHZ~ z;9LnuU}0D@+=ly|oy+1cT(|%cN>$|2Y;OJ!geqpcLdt@B&nlCf$U!PE_Xd8L*6xY^ zd^5roF=G==to+h0Yy)9?)!l82=@p?gw_D%5;i?4dY;85JdrRvMqyVkgHka7W|HLK} zPs~mr_|xV-%b+PcOn((Skl@*6yM7bkfWD~>f@x6oiR7-biU3aCHy8lr(X4{IM>F&Z zSPOUhU#y0}Kp=OCQP+2BcsSBQu7rpzMdX$Cv5(v2Ru2GG^mB6yfB|3bmbc1Nq%O`2 zIjq4{rxP?Ke3@H^hmbY~l|T8jQ%L9<%92UeD9$~K-rVMW!A>KiB&h0%bgd}WOQhWpP}q_@~hq0n`4+$T9lV}$a~6LLL%og zYw$=jYoHcX5QqSO3zIl8dtTXa3vagJ6dw9Eq}?ED2jBh3XVAHlTej?#UwRFAV*sIC zZ7H{iA&-NTGt%f>L+noklwHBLn&=FasCebj8m3GsEiK13$!1^*`4+dH!RxVc|oZmOxLys$&Pz6zT*VE4_L0>2Y0v zT+PkD(%b7upNkP58lW|j3hB-F(X^n}L-|p#_WnOFK_+B__?~j9|1c6`AKTlHfo%rT zvLya8@YE?|*`g&9!^kSKKhj`k(b6ptR#=lH5T!Q+h{U1TQA|)Ykf4Y;!G~}k;dIz< zE2oWaZf&fr&^NF&VkeKe> z)o%GEmg3}&1;8}G@FREus56~2IVlg0gHYR|zM`*&NB}5H2$X+q9PU9AZ;wJ;KXVm; zfuTg-or^D(aVAf};v;91KgbV?YEUrP%$C*`p@Jfj4PFs**!e;#lODBC%7y+MYN#UV zGry3B|!#C;*9YV?udp`4__y9i`a2)FZwg3;U;MND?!+c zv!<-ybeM%m1uAwZxhYFeQprnWdOk@PAvRARM41Gzl7Nv(fWfOV5Gom~?Ovo{yms)s z3*k&-TnE%1-3S24)`Fq=5l)w+N~P`+bp{wpCXb;YLJsz6Z&T#PgH4vTJ~&h-Zj>JUkq8S>l|bj;PsTK1$<|Ms$RD;sQ6V zX#K8S*uFX*jR&=^W#JFM#~HdU&kWtxkc6;ASI01pCpk1hUdo^FRR>?~fy zV@I)e+XD4ODudRdV@*RJI-tm1MqQn)xq1*st4!qf=eUVW5LM{Jdf%9SAvAX82I?RmOVA++?&#dZ9mv3V4+_K~ znMltx-NY2+u+iDEX4s@847D!wC@9jHO39}k zJQXpbp{dF1B{e+55z7Z1d>TpRNv>9%t1q++jkMx~Sj>&dv{$E@#tqGg2LhceOAl|O zGI^k3x@CemN-4k}Mlz)6JZlXN#UHB2gAbh6$2mw}=G&BpSii!XZj^o74mYWjAzWyJ z-{nJ@gq%UBFd@|T(;g^*bbXll`)-(~-sH1h&#V4%C>y95arc4>?ZWJ!uaY=J7Tk*h z4W&xVJ6k9k^~gcMF;Lb3EGS+FV}^)t)!7P;C_~pOfPQ#o*#y)KeW_-1PfTRl+$=~W zV_qq`;B{9o;rtA8%xd_?kU1563Tk&3m3l?yzGQ@Cv9Nv(&yVucz7c2s>zq55rpz3q$GsCy9AW zaqQDg&XZCw@M5c<3)MN7KApbu;9> z?oT(C10pBwz4VRids!eMnVx^_(ZXg$u@n?(6n_fX2E%bTb-#x6ZYl6ETF+5CZ~_8! z7NaWPbnra)Cw~5G=p;K1Y^V za@8~>I~}70fVU9cwJVelM?Xq}b8AfE3#gwGuUryp5#zj#tpsPI`6#ZB$ z0#3?WZsHCZj?RkcEdudOIf%jsc5eH@rjJJHW0tuLMH1NcMy#LX=x(@;cdzLYku*nb zF;~+jj$WNyMlFeD;%#A6>DW+I9AQk%igs0z^IwM>R|PYpHDA0c*g+IrLYRl^5?6qs zdK_yEEu{Xvh+`ONhp%c1&jzD40zsSXOXJm8IV3XcSugyed?+=HF|cw0C(RA;QXmGv zYj63XM9&g4wI{9cdJupwv+MTJ>AyhzE>tc9elJy`*6XY+tkLtNc1mqQmdz8S2;IRP zis$0}?w(S4hk$o_dtpFxM?3s3VrO>ola8fdiWvV$tGpBJN>f5M5@RrcG(5=Mn}Xi9 zJ}+Vx@~%X$H94FcIwHXD{2uSYvfk6EGDf^&U>3EFc&@tfi7VAUrv2)ai|u-e&)IFb z?_Gv)hHZmeF*dUlNa>K5B^2eNGFAVsl9*@a=IZT%XJTfk;{Pqy7#!28JVzTKHlVC9@2>qM5OrEi#5xWwDUPT>4sPi^ZIasNj zQdy~ScS(ZK!5)lvga!cWA%U`s2WwxAA^P(3=Uhu!MI|LPT^sFEHn9Gy7lxs|FfZ?C z@>lu)h@3@3W`6uIpM8-lN6pTzvj}Ef6!>?>SQz5@e-eWJFHQI#xzc|l%m2Ue+c_da zLJCrEVrD#>emZ17?i;th*{Wx|%Xe|xnbS#?xgkYXvfSU6*NPqp|51|nvxo2e!Q)-~ zij_a6N;T`)Z5rBYYOn9E$ttH_k#Qw$w+2>R05BRKzy4K?<>gV(Ue>V(dGP0tEmISA zh23+bDvibq%j#3_{&8Bi*SMBup+WWu3{-Er8lhVZIUMn)7n1&OZdMNsE|aHnCzNKE zENs*lJ8FuEk+=~?UMqIZRQ_kktDsp@Zc z+_L0)4l%0ru+w9{v-x5E-d6L)+{`*Ur;8gLixVV<3&!~>SA=`XO(Wn$Md5;B_^zQi+`NRc`0E(Hit0;sT7?Z9pP$VB@y#L zD0)yckP@Slvac!rVo)u&PsZn#Q0}a}!u*`V2jltEw;uitG01sQ9O4{$@5H0itBWPe z)qJCTj|YykZeJA-o-efMm0z)_%?&xGJ3yi2ts@Po2DA%@F3rdrL9mJA`q1l*G$Lzr zLy)IF1G5HNsF9y{R#r$`qEpLYKNDNB_*&yk-6kyXRqUeOV%@E>=4OW6rM~e6_7}_7 z9S4QHy!cnY>ZBD2d4Kap;H$xns*&AR7f7b$HCL~BvihW)FupVJX5W?7*~HU7x|+wm z#{+%l5Jl`eP&JEU4NftZmTL=1AJgg~)F7~A_P7;5#21Q71)ep-I^1nYnUZ<=Yys zs`n>nIDQAMqzYEWJYw2~UBDE4*y;X<)2OPrSS)vNh1+d_qlRD{B#cp*rQyd(XICg2 z0zZeix`&9y#4UIPJre{rxcD4UbjvoLz7@fw>Y zty);~m+G5^#ArDvE#F~N0{rY}dalZFw<+R_u->7bEV6gpO|DdOlZrc0-lL~zf zAqPsJYZ{#gWnFxg?|##PBcBpXV#LRO*!K#rdGQz;KtzkKhgJ^t|JO( zoc<0Qzltb!n5F9*?pne-13#cRukkUG0|MiSGyr^$SS`3SFj8~uKs-jR#H(81JW$}GYS6Pf&bz7j zz<|n=h{MHu2R7fcHg^87T~=_l-G}-i=c4KL(w?5~lN#zt714GRyc*ToWuD!!b7j}$ z=}SOdhBNFRD2TS2%%5|cXmk3~6>sAmsK_}q(biPF@j!o>{-I=w<9LC zb5mn-IG>k#IKA6PFn?H#uz}U}C@8H=ZaR=IF);*}*a#)Qf?W}@u|4`}UthohGmduI z4(+wXsy-wPps!%^Yl=kyW|J%G{rWZ`>J+NQ@`x4 z`NzjGtT#gwO3w*-7vyx_w|avwu6NswoYPA0^A%Gb)`*HBvV!=z2d`buX+Sk6|$d8TMkpo zs7Ww0N}`lksu79B*0T);%*HZ(*0eedAKgk4x>t zXKr?Bt*aj@seE~XVcY1}uj?B0Pe6t)Yua#RH)I@?eGn{jZFeaB4%s?tNumUTNXg02FJFT{50FyCB zVN}JXmJMOMGF67a?uN{LX8f|YY^%Ptf&FRjtdhdR1&lYOR&HhI#}+=gntFKPwnd?? zfw_d_{?0>xp17MIa5=mF;m1iU)&8bB-hd+t_TRgI42o&Iy4S8WddT_oYRRIdPuAV+ zJ9iI%C(FDoVwh+C(xsHK^Who2a|eUouXFs;{oIGTUV!UQ{nm2hlW*qolD9>Th?Kr9 zQ|ajFY^`@O%8Xwq!&j)%4Q6AbOLY}oyc|kqO4jR^(0=Mo%7Iu)yMeDAnO1lvoEg{E z4lyzP{X24xfpY$Qv)ZTndi`x-vNriFzCygb4{7SKLLpTgAYIH=j=V_R3fTh9Nyk~{ z07{ZF+)g+y0H&rI5TBmICvbA(q2!We1@}#5W8Y0zSJ|DospCF6iV1(5GtMt5v{RaW zKCx_@=a713#;>8^mz#ZOHo$oH=lVHyS@*%u0%f06O)B1X+6J9qJ^3@S`;C2xLu-~( zfPu+!+lv(b=~lDTBRe?tqL|kxP8#psA?Oh3%4(A~=9M?q*?57`m!iu)F$HA`BoZQ1 z2Emj}tDk4-3UdidzCJ+P^jl_f5i6D&km6v`9m48~gafQoih7ba9_(s7daTy)W#Zke zZ{guISW6F)L90VIRUpzQ&7cHtNJCmjp^c1TU8{UQ`J!NXf}@8!^R;j4irx!vH;y-7 zv)uPkC60Y4=u^y-#hsux18(W%AqPaCgYzygPfh&ezF%8IcVq{!bIfIW{&i8>r@4K1 ze^B;#)P|m0e}~6L!mBig_c=bkYF@H5xLRYc%2A{2d}t)mRUkQ)ErB9eHhJjY>DP;V zk3TyP`fmASl=ywxeK<5cv8{M*_aahOC|(E+DHVyV!zO|gCw_p7a^1ZhaHi+xbibHR zgmshh+?A%ke?N*mI5N-J__+Ap+qdIjw|jpd&@h&WY?VZm_IqwA(o^>zn4xh8b~ZGu z$?56tHoP_bVhx%gIMw@cNtwA{u!jb;uzCn6@vwbZb(dvQ{3x)sr)pZi+lPqQ*38E5_uk8c#!`%`@t5fx!C3xlUqt| z^Zt3_^Q?;B`apx@?+JDlO?wOTr>7Znt~D^}2s+TO{=6s57yA0~*w03dd8)Zem(=lZ z)ii&T2^&EnxtwnG(qGAavzGiHcAENSi6He0aR`Mz$UlQatbT;ms$+ETQB@N{W0S9JI$XujQYmB3M9J@PcB3o-wF|29?(#q`cdX@lulji+;)wB+;iX@ z->=U$jnh55&NaAaxcp$eU|rDE__kDi%@??EgRT9}`EI-epzO!4? zAdPA0PfUyOY3}*ubD6O&ZHG-+uK<{HJIb~}j32u$HVaFpukckwh{LA`S;ai$SdvRp zMH_W}6Lovvy!ipsBS|EC8V}%Yq0PFoBG{oo$OF#pUY(KD~2DA(R5ITN}zxWI0hI1KbfmpAG-C*&f+m9YV?*% zzu9~#S)3&Ye*QA(C21$DBMqOWE4?V&B9cmukoQeg&9WckR|?3KUDqp|!2OAdEXBWu zN!3@q;Y-^rCBvj(%7mMGB}q@A>8jLXSre0}BLCgp0uovZ(wg=*&g*wX%QkMl}Her|%m1KLAUL(jWi; literal 0 HcmV?d00001 diff --git a/docs/documentation/docs/assets/termpicker-empty.png b/docs/documentation/docs/assets/termpicker-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..c8587b23b88577fc5ac78469a75c2c39ef018087 GIT binary patch literal 1112 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 Xpni7$@KlBR7Vz6=<6wQ8=#}sv&YI0a literal 0 HcmV?d00001 diff --git a/docs/documentation/docs/assets/termpicker-selected-terms.png b/docs/documentation/docs/assets/termpicker-selected-terms.png new file mode 100644 index 0000000000000000000000000000000000000000..05119c8e293c201ee1315a69c96121d7360cffd8 GIT binary patch literal 3930 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 **Disclaimer**: This control makes use of the `ProcessQuery` API end-points to retrieve the managed metadata information. This will get changed once the APIs for managing managed metadata will become available. + +**Empty term picker** + +![Empty term picker](../assets/termpicker-empty.png) + +**Selecting terms** + +![Selecting terms](../assets/termpicker-selection.png) + +**Selected terms in picker** + +![Selected terms in the input](../assets/termpicker-selected-terms.png) + +**Term picker: Auto Complete** + +![Selected terms in the input](../assets/termpicker-autocomplete.png) + + +## How to use this control in your solutions + +- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../#getting-started) page for more information about installing the dependency. +- Import the following modules to your component: + +```TypeScript +import { TaxonomyPicker, IPickerTerms } from "@pnp/spfx-controls-react/lib/TaxonomyPicker"; +``` + +- Use the `TaxonomyPicker` control in your code as follows: + +```TypeScript + +``` + +- With the `onChange` property you can capture the event of when the terms in the picker has changed: + +```typescript + private _onTaxPickerChange(terms : IPickerTerms) + { + console.log("Terms", terms); + } +``` + +## Implementation + +The TaxonomyPicker control can be configured with the following properties: + +| Property | Type | Required | Description | +| ---- | ---- | ---- | ---- | +| panelTitle | string | yes | TermSet Picker Panel title. | +| label | string | yes | Text displayed above the Taxonomy Picker. | +| disabled | string | no | Specify if the control needs to be disabled. | +| context | WebPartContext | yes | Context of the current web part. | +| initialValues | IPickerTerms | no | Defines the selected by default term sets. | +| allowMultipleSelections | boolean | no | Defines if the user can select only one or many term sets. Default value is false. | +| termsetNameOrID | string | yes | The name or Id of your TermSet that you would like the Taxonomy Picker to chose terms from. | +| onChange | function | no | captures the event of when the terms in the picker has changed. | +| isTermSetSelectable | boolean | no | Specify if the term set itself is selectable in the tree view. | +| anchorId | string | no | Set the anchorid to a child term in the termset to be able to select terms from that level and below. | + +Interface `IPickerTerm` + +| Property | Type | Required | Description | +| ---- | ---- | ---- | ---- | +| key | string | yes | The ID of the term | +| name | string | yes | The name of the term | +| path | string | yes | The path of the term | +| termSet | string | yes | The Id of the parent term set of the term | +| termSetName | string | no | The Name of the parent term set of the term | + +Interface `IPickerTerms` + +An Array of IPickerTerm + +![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/Placeholder) diff --git a/docs/documentation/docs/index.md b/docs/documentation/docs/index.md index 14ae85f81..8d51db1ea 100644 --- a/docs/documentation/docs/index.md +++ b/docs/documentation/docs/index.md @@ -33,6 +33,7 @@ The following controls are currently available: - [ListView](./controls/ListView) (List view control) - [Placeholder](./controls/Placeholder) (Control that can be used to show an initial placeholder if the web part has to be configured) - [SiteBreadcrumb](./controls/SiteBreadcrumb) (Breadcrumb control) +- [SiteBreadcrumb](./controls/TaxonomyPicker) (Taxonomy Picker) - [WebPartTitle](./controls/WebPartTitle) (Customizable web part title control) - [IFrameDialog](./controls/IFrameDialog) (renders a Dialog with an iframe as a content) diff --git a/docs/documentation/mkdocs.yml b/docs/documentation/mkdocs.yml index 1f4ea3e99..eef83bac7 100644 --- a/docs/documentation/mkdocs.yml +++ b/docs/documentation/mkdocs.yml @@ -8,6 +8,7 @@ pages: - Placeholder: 'controls/Placeholder.md' - SiteBreadcrumb: 'controls/SiteBreadcrumb.md' - WebPartTitle: 'controls/WebPartTitle.md' + - TaxonomyPicker: 'controls/TaxonomyPicker.md' - IFrameDialog: 'controls/IFrameDialog.md' - 'Field Controls': - 'Getting started': 'controls/fields/main.md' diff --git a/src/controls/taxonomyPicker/ITaxonomyPicker.ts b/src/controls/taxonomyPicker/ITaxonomyPicker.ts index 9e4be97ca..c114ea581 100644 --- a/src/controls/taxonomyPicker/ITaxonomyPicker.ts +++ b/src/controls/taxonomyPicker/ITaxonomyPicker.ts @@ -34,7 +34,7 @@ export interface ITaxonomyPickerProps { /** * Id of a child term in the termset where to be able to selected and search the terms from */ - ancoreId?: string; + anchorId?: string; /** * Specify if the term set itself is selectable in the tree view */ diff --git a/src/controls/taxonomyPicker/TaxonomyPicker.tsx b/src/controls/taxonomyPicker/TaxonomyPicker.tsx index dfa543f1b..1dce34d85 100644 --- a/src/controls/taxonomyPicker/TaxonomyPicker.tsx +++ b/src/controls/taxonomyPicker/TaxonomyPicker.tsx @@ -280,7 +280,7 @@ export class TaxonomyPicker extends React.Component

{this.state.termSetAndTerms.Name}

- TaxonomyPicker tester: Date: Fri, 20 Apr 2018 17:55:04 -0400 Subject: [PATCH 30/35] Change Dislpay to Display --- docs/documentation/docs/controls/fields/FieldTitleRenderer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/docs/controls/fields/FieldTitleRenderer.md b/docs/documentation/docs/controls/fields/FieldTitleRenderer.md index e14095bcd..1da473cef 100644 --- a/docs/documentation/docs/controls/fields/FieldTitleRenderer.md +++ b/docs/documentation/docs/controls/fields/FieldTitleRenderer.md @@ -1,6 +1,6 @@ # FieldTitleRenderer control -This control renders title either as a simple text or as a link to the Dislpay Form. Additional actions like Share and Context Menu are not implemented. +This control renders title either as a simple text or as a link to the Display Form. Additional actions like Share and Context Menu are not implemented. ![FieldTitleRenderer control output](../../assets/FieldTitleRenderer.png) From bfd7591789b4d51aae73f84f4285de6c583b1c74 Mon Sep 17 00:00:00 2001 From: Hugo Bernier Date: Fri, 20 Apr 2018 17:59:47 -0400 Subject: [PATCH 31/35] Changed Dislpay to Display --- docs/documentation/docs/controls/fields/main.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/docs/controls/fields/main.md b/docs/documentation/docs/controls/fields/main.md index d24cebf7e..9c4644e04 100644 --- a/docs/documentation/docs/controls/fields/main.md +++ b/docs/documentation/docs/controls/fields/main.md @@ -52,7 +52,7 @@ The following Field Controls are currently available: - [FieldNameRenderer](./FieldNameRenderer) (renders document's name as a link) - [FieldTaxonomyRenderer](./FieldTaxonomyRenderer) (renders terms from Managed Metadata field) - [FieldTextRenderer](./FieldTextRenderer) (renders simple text) -- [FieldTitleRenderer](./FieldTitleRenderer) (renders title either as a simple text or as a link to the Dislpay Form) +- [FieldTitleRenderer](./FieldTitleRenderer) (renders title either as a simple text or as a link to the Display Form) - [FieldUrlRenderer](./FieldUrlRenderer) (renders Hyperlink or Picture field value as a link or image) - [FieldUserRenderer](./FieldUserRenderer) (renders each referenced user/group as a link on a separate line) From 95f62d2e239b1abc932ac6546dc1815c131ca570 Mon Sep 17 00:00:00 2001 From: Hugo Bernier Date: Fri, 20 Apr 2018 18:01:12 -0400 Subject: [PATCH 32/35] Changed Dislpay to Display --- docs/documentation/docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/docs/index.md b/docs/documentation/docs/index.md index 14ae85f81..15290613a 100644 --- a/docs/documentation/docs/index.md +++ b/docs/documentation/docs/index.md @@ -47,7 +47,7 @@ Field customizer controls: - [FieldNameRenderer](./controls/fields/FieldNameRenderer) (renders document's name as a link) - [FieldTaxonomyRenderer](./controls/fields/FieldTaxonomyRenderer) (renders terms from Managed Metadata field) - [FieldTextRenderer](./controls/fields/FieldTextRenderer) (renders simple text) -- [FieldTitleRenderer](./controls/fields/FieldTitleRenderer) (renders title either as a simple text or as a link to the Dislpay Form) +- [FieldTitleRenderer](./controls/fields/FieldTitleRenderer) (renders title either as a simple text or as a link to the Display Form) - [FieldUrlRenderer](./controls/fields/FieldUrlRenderer) (renders Hyperlink or Picture field value as a link or image) - [FieldUserRenderer](./controls/fields/FieldUserRenderer) (renders each referenced user/group as a link on a separate line) From 0ad67caeffac57cfe8305d1af9c488b9a270a1e7 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 25 Apr 2018 19:11:43 +0200 Subject: [PATCH 33/35] Small documentation updates --- .../docs/controls/TaxonomyPicker.md | 26 +++++++++---------- docs/documentation/docs/index.md | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/documentation/docs/controls/TaxonomyPicker.md b/docs/documentation/docs/controls/TaxonomyPicker.md index 52b143d42..8244b5204 100644 --- a/docs/documentation/docs/controls/TaxonomyPicker.md +++ b/docs/documentation/docs/controls/TaxonomyPicker.md @@ -1,8 +1,9 @@ # Taxonomy Picker -This control Allows you to select one more more Terms from a termset via its name or termset ID. You can also configure the control to select the child terms from a specific term in the termset by setting the AnchorId. +This control Allows you to select one or more Terms from a TermSet via its name or TermSet ID. You can also configure the control to select the child terms from a specific term in the TermSet by setting the AnchorId. -> **Disclaimer**: This control makes use of the `ProcessQuery` API end-points to retrieve the managed metadata information. This will get changed once the APIs for managing managed metadata will become available. +!!! note "Disclaimer" + This control makes use of the `ProcessQuery` API end-points to retrieve the managed metadata information. This will get changed once the APIs for managing managed metadata will become available. **Empty term picker** @@ -35,11 +36,11 @@ import { TaxonomyPicker, IPickerTerms } from "@pnp/spfx-controls-react/lib/Taxon ```TypeScript ``` @@ -47,10 +48,9 @@ import { TaxonomyPicker, IPickerTerms } from "@pnp/spfx-controls-react/lib/Taxon - With the `onChange` property you can capture the event of when the terms in the picker has changed: ```typescript - private _onTaxPickerChange(terms : IPickerTerms) - { - console.log("Terms", terms); - } +private onTaxPickerChange(terms : IPickerTerms) { + console.log("Terms", terms); +} ``` ## Implementation @@ -65,10 +65,10 @@ The TaxonomyPicker control can be configured with the following properties: | context | WebPartContext | yes | Context of the current web part. | | initialValues | IPickerTerms | no | Defines the selected by default term sets. | | allowMultipleSelections | boolean | no | Defines if the user can select only one or many term sets. Default value is false. | -| termsetNameOrID | string | yes | The name or Id of your TermSet that you would like the Taxonomy Picker to chose terms from. | +| TermSetNameOrID | string | yes | The name or Id of your TermSet that you would like the Taxonomy Picker to chose terms from. | | onChange | function | no | captures the event of when the terms in the picker has changed. | -| isTermSetSelectable | boolean | no | Specify if the term set itself is selectable in the tree view. | -| anchorId | string | no | Set the anchorid to a child term in the termset to be able to select terms from that level and below. | +| isTermSetSelectable | boolean | no | Specify if the TermSet itself is selectable in the tree view. | +| anchorId | string | no | Set the anchorid to a child term in the TermSet to be able to select terms from that level and below. | Interface `IPickerTerm` @@ -77,8 +77,8 @@ Interface `IPickerTerm` | key | string | yes | The ID of the term | | name | string | yes | The name of the term | | path | string | yes | The path of the term | -| termSet | string | yes | The Id of the parent term set of the term | -| termSetName | string | no | The Name of the parent term set of the term | +| termSet | string | yes | The Id of the parent TermSet of the term | +| termSetName | string | no | The Name of the parent TermSet of the term | Interface `IPickerTerms` diff --git a/docs/documentation/docs/index.md b/docs/documentation/docs/index.md index e2a05dc67..bc1dc0932 100644 --- a/docs/documentation/docs/index.md +++ b/docs/documentation/docs/index.md @@ -39,7 +39,8 @@ The following controls are currently available: Field customizer controls: -> **Note**: If you want to use these controls in your solution, first check out the start guide for these controls: [using the field controls](./controls/fields/main). +!!! note + If you want to use these controls in your solution, first check out the start guide for these controls: [using the field controls](./controls/fields/main). - [FieldAttachmentsRenderer](./controls/fields/FieldAttachmentsRenderer) (renders Clip icon based on the provided `count` property is defined and greater than 0) - [FieldDateRenderer](./controls/fields/FieldDateRenderer) (renders date string as a simple text) From 7aa383220e83a3d236fa20634c201dd014bb13a7 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 25 Apr 2018 20:28:24 +0200 Subject: [PATCH 34/35] Various fixes for the list picker --- src/controls/listPicker/IListPicker.ts | 119 ++++---- src/controls/listPicker/ListPicker.tsx | 262 ++++++++++-------- src/services/SPService.ts | 54 ++-- src/services/SPServiceFactory.ts | 15 +- src/services/SPServiceMock.ts | 68 +++-- .../controlsTest/components/ControlsTest.tsx | 30 +- 6 files changed, 295 insertions(+), 253 deletions(-) diff --git a/src/controls/listPicker/IListPicker.ts b/src/controls/listPicker/IListPicker.ts index bf2bd32fc..f4e4d9b9c 100644 --- a/src/controls/listPicker/IListPicker.ts +++ b/src/controls/listPicker/IListPicker.ts @@ -1,67 +1,68 @@ +import { ApplicationCustomizerContext } from '@microsoft/sp-application-base'; import IWebPartContext from "@microsoft/sp-webpart-base/lib/core/IWebPartContext"; import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; - +import { WebPartContext } from '@microsoft/sp-webpart-base'; import { LibsOrderBy } from "../../services/ISPService"; export interface IListPickerProps { - /** - * The web part context - */ - context: IWebPartContext; - /** - * If provided, additional class name to provide on the dropdown element. - */ - className?: string; - /** - * Whether or not the control is disabled - */ - disabled?: boolean; - /** - * The SharePoint BaseTemplate to filter the list options by - */ - baseTemplate?: number; - /** - * Whether or not to include hidden lists. Default is true - */ - includeHidden?: boolean; - /** - * How to order the lists retrieved from SharePoint - */ - orderBy?: LibsOrderBy; - /** - * Keys of the selected item(s). If you provide this, you must maintain selection - * state by observing onSelectionChanged events and passing a new value in when changed. - */ - selectedList?: string | string[]; - /** - * Optional mode indicates if multi-choice selections is allowed. Default to false - */ - multiSelect?: boolean; - /** - * The label to use - */ - label?: string; - /** - * Input placeholder text. Displayed until option is selected. - */ - placeHolder?: string; - /** - * Callback issues when the selected option changes - */ - onSelectionChanged?: (newValue: string | string[]) => void; + /** + * The web part context + */ + context: WebPartContext | ApplicationCustomizerContext; + /** + * If provided, additional class name to provide on the dropdown element. + */ + className?: string; + /** + * Whether or not the control is disabled + */ + disabled?: boolean; + /** + * The SharePoint BaseTemplate to filter the list options by + */ + baseTemplate?: number; + /** + * Whether or not to include hidden lists. Default is true + */ + includeHidden?: boolean; + /** + * How to order the lists retrieved from SharePoint + */ + orderBy?: LibsOrderBy; + /** + * Keys of the selected item(s). If you provide this, you must maintain selection + * state by observing onSelectionChanged events and passing a new value in when changed. + */ + selectedList?: string | string[]; + /** + * Optional mode indicates if multi-choice selections is allowed. Default to false + */ + multiSelect?: boolean; + /** + * The label to use + */ + label?: string; + /** + * Input placeholder text. Displayed until option is selected. + */ + placeHolder?: string; + /** + * Callback issues when the selected option changes + */ + onSelectionChanged?: (newValue: string | string[]) => void; } export interface IListPickerState { - /** - * The options available to the listPicker - */ - options: IDropdownOption[]; - /** - * Whether or not the listPicker options are loading - */ - loading: boolean; - /** - * Keys of the currently selected item(s). - */ - selectedList?: string | string[]; -} \ No newline at end of file + /** + * The options available to the listPicker + */ + options: IDropdownOption[]; + /** + * Whether or not the listPicker options are loading + */ + loading: boolean; + /** + * Keys of the currently selected item(s). + */ + selectedList?: string | string[]; +} diff --git a/src/controls/listPicker/ListPicker.tsx b/src/controls/listPicker/ListPicker.tsx index db1c4d1b6..053a00300 100644 --- a/src/controls/listPicker/ListPicker.tsx +++ b/src/controls/listPicker/ListPicker.tsx @@ -1,142 +1,164 @@ import * as React from 'react'; import { IDropdownOption, IDropdownProps, Dropdown } from 'office-ui-fabric-react/lib/components/Dropdown'; import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/components/Spinner'; - import { IListPickerProps, IListPickerState } from './IListPicker'; import { ISPService } from '../../services/ISPService'; import { SPServiceFactory } from '../../services/SPServiceFactory'; +import * as appInsights from '../../common/appInsights'; import styles from './ListPicker.module.scss'; /** - * Empty list value, to be checked for single list selection - */ +* Empty list value, to be checked for single list selection +*/ const EMPTY_LIST_KEY = 'NO_LIST_SELECTED'; /** - * Renders the controls for the ListPicker component - */ +* Renders the controls for the ListPicker component +*/ export class ListPicker extends React.Component { - private _options: IDropdownOption[] = []; - private _selectedList: string | string[]; - - /** - * Constructor method - */ - constructor(props: IListPickerProps) { - super(props); - - console.debug('selectedList', this.props.selectedList); - - this.state = { - options: this._options, - loading: false - }; - - this.onChanged = this.onChanged.bind(this); - } - - /** - * Lifecycle hook when component is mounted - */ - public componentDidMount() { - this.loadLists(); + private _options: IDropdownOption[] = []; + private _selectedList: string | string[]; + + /** + * Constructor method + */ + constructor(props: IListPickerProps) { + super(props); + + appInsights.track('ReactListPicker'); + + this.state = { + options: this._options, + loading: false + }; + + this.onChanged = this.onChanged.bind(this); + } + + /** + * Lifecycle hook when component is mounted + */ + public componentDidMount() { + this.loadLists(); + } + + /** + * componentDidUpdate lifecycle hook + * @param prevProps + * @param prevState + */ + public componentDidUpdate(prevProps: IListPickerProps, prevState: IListPickerState): void { + if ( + prevProps.baseTemplate !== this.props.baseTemplate || + prevProps.includeHidden !== this.props.includeHidden || + prevProps.orderBy !== this.props.orderBy || + prevProps.selectedList !== this.props.selectedList + ) { + this.loadLists(); } + } + + /** + * Loads the list from SharePoint current web site + */ + private loadLists() { + const { context, baseTemplate, includeHidden, orderBy, multiSelect, selectedList } = this.props; + + // Show the loading indicator and disable the dropdown + this.setState({ loading: true }); + + const service: ISPService = SPServiceFactory.createService(context, true, 5000); + service.getLibs({ + baseTemplate: baseTemplate, + includeHidden: includeHidden, + orderBy: orderBy + }).then((results) => { + // Start mapping the lists to the dropdown option + results.value.map(list => { + this._options.push({ + key: list.Id, + text: list.Title + }); + }); - /** - * Loads the list from SharePoint current web site - */ - private loadLists() { - const { context, baseTemplate, includeHidden, orderBy, multiSelect, selectedList } = this.props; - - // Show the loading indicator and disable the dropdown - this.setState({ loading: true }); - - const service: ISPService = SPServiceFactory.createService(context, true, 5000); - service.getLibs({ - baseTemplate: baseTemplate, - includeHidden: includeHidden, - orderBy: orderBy - }).then((results) => { - // Start mapping the lists to the dropdown option - results.value.map(list => { - this._options.push({ - key: list.Id, - text: list.Title - }); - }); - - if (multiSelect !== true) { - // Add option to unselct list - this._options.unshift({ - key: EMPTY_LIST_KEY, - text: '' - }); - } - - this._selectedList = this.props.selectedList; - - // Hide the loading indicator and set the dropdown options and enable the dropdown - this.setState({ - loading: false, - options: this._options, - selectedList: this._selectedList - }); + if (multiSelect !== true) { + // Add option to unselct list + this._options.unshift({ + key: EMPTY_LIST_KEY, + text: '' }); + } + + this._selectedList = this.props.selectedList; + + // Hide the loading indicator and set the dropdown options and enable the dropdown + this.setState({ + loading: false, + options: this._options, + selectedList: this._selectedList + }); + }); + } + + /** + * Raises when a list has been selected + * @param option the new selection + * @param index the index of the selection + */ + private onChanged(option: IDropdownOption, index?: number): void { + const { multiSelect, onSelectionChanged } = this.props; + + if (multiSelect === true) { + if (!this._selectedList) { + this._selectedList = [] as string[]; + } + + const selectedLists: string[] = this._selectedList as string[]; + // Check if option was selected + if (option.selected) { + selectedLists.push(option.key as string); + } else { + // Filter out the unselected list + this._selectedList = selectedLists.filter(list => list !== option.key); + } + } else { + this._selectedList = option.key as string; } - /** - * Raises when a list has been selected - * @param option the new selection - * @param index the index of the selection - */ - private onChanged(option: IDropdownOption, index?: number): void { - const { multiSelect, onSelectionChanged } = this.props; - - if (multiSelect === true) { - if (this._selectedList === undefined) { - this._selectedList = new Array(); - } - (this._selectedList as string[]).push(option.key as string); - } else { - this._selectedList = option.key as string; - } - - if (onSelectionChanged) { - onSelectionChanged(this._selectedList); - } + if (onSelectionChanged) { + onSelectionChanged(this._selectedList); } - - /** - * Renders the ListPicker controls with Office UI Fabric - */ - public render(): JSX.Element { - const { loading, options, selectedList } = this.state; - const { className, disabled, multiSelect, label, placeHolder } = this.props; - - const dropdownOptions: IDropdownProps = { - className: className, - options: options, - disabled: ( loading || disabled ), - label: label, - placeHolder: placeHolder, - onChanged: this.onChanged - }; - - if (multiSelect === true) { - dropdownOptions.multiSelect = true; - dropdownOptions.selectedKeys = selectedList as string[]; - } else { - dropdownOptions.selectedKey = selectedList as string; - } - - return ( -
- { loading && } - -
- ); + } + + /** + * Renders the ListPicker controls with Office UI Fabric + */ + public render(): JSX.Element { + const { loading, options, selectedList } = this.state; + const { className, disabled, multiSelect, label, placeHolder } = this.props; + + const dropdownOptions: IDropdownProps = { + className: className, + options: options, + disabled: ( loading || disabled ), + label: label, + placeHolder: placeHolder, + onChanged: this.onChanged + }; + + if (multiSelect === true) { + dropdownOptions.multiSelect = true; + dropdownOptions.selectedKeys = selectedList as string[]; + } else { + dropdownOptions.selectedKey = selectedList as string; } -} -export default ListPicker; \ No newline at end of file + return ( +
+ { loading && } + +
+ ); + } +} diff --git a/src/services/SPService.ts b/src/services/SPService.ts index 99de2c256..a68bbd60a 100644 --- a/src/services/SPService.ts +++ b/src/services/SPService.ts @@ -1,32 +1,36 @@ import { ISPService, ILibsOptions, LibsOrderBy } from "./ISPService"; import { ISPLists } from "../common/SPEntities"; -import { IWebPartContext } from "@microsoft/sp-webpart-base"; +import { WebPartContext } from "@microsoft/sp-webpart-base"; +import { ApplicationCustomizerContext } from '@microsoft/sp-application-base'; import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http"; -export class SPService implements ISPService { - private readonly _context: IWebPartContext; - constructor(context: IWebPartContext) { - this._context = context; +export default class SPService implements ISPService { + + constructor(private _context: WebPartContext | ApplicationCustomizerContext) {} + + /** + * Get lists or libraries + * @param options + */ + public getLibs(options?: ILibsOptions): Promise { + let filtered: boolean; + let queryUrl: string = `${this._context.pageContext.web.absoluteUrl}/_api/web/lists?$select=Title,id,BaseTemplate`; + + if (options.orderBy) { + queryUrl += `&$orderby=${options.orderBy === LibsOrderBy.Id ? 'Id': 'Title'}`; + } + + if (options.baseTemplate) { + queryUrl += `&$filter=BaseTemplate eq ${options.baseTemplate}`; + filtered = true; } - public getLibs(options?: ILibsOptions): Promise { - let filtered: boolean; - let queryUrl: string = `${this._context.pageContext.web.absoluteUrl}/_api/web/lists?$select=Title,id,BaseTemplate`; - if (options.orderBy !== null) { - queryUrl += `&$orderby=${options.orderBy === LibsOrderBy.Id ? 'Id': 'Title'}`; - } - if (options.baseTemplate !== null) { - queryUrl += `&$filter=BaseTemplate eq ${options.baseTemplate}`; - filtered = true; - } - if (options.includeHidden === false) { - queryUrl += filtered ? ' and Hidden eq false' : '&$filter=Hidden eq false'; - filtered = true; - } - return this._context.spHttpClient.get(queryUrl, SPHttpClient.configurations.v1) - .then((response: SPHttpClientResponse) => { - return response.json(); - }) as Promise; + + if (options.includeHidden === false) { + queryUrl += filtered ? ' and Hidden eq false' : '&$filter=Hidden eq false'; + filtered = true; } -} -export default SPService; \ No newline at end of file + return this._context.spHttpClient.get(queryUrl, SPHttpClient.configurations.v1) + .then(response => response.json()) as Promise; + } +} diff --git a/src/services/SPServiceFactory.ts b/src/services/SPServiceFactory.ts index f26bfdff0..9870bec76 100644 --- a/src/services/SPServiceFactory.ts +++ b/src/services/SPServiceFactory.ts @@ -1,14 +1,15 @@ -import { IWebPartContext } from "@microsoft/sp-webpart-base"; +import { ApplicationCustomizerContext } from '@microsoft/sp-application-base'; +import { IWebPartContext, WebPartContext } from "@microsoft/sp-webpart-base"; import { ISPService } from "./ISPService"; import { Environment, EnvironmentType } from "@microsoft/sp-core-library"; import SPServiceMock from "./SPServiceMock"; import SPService from "./SPService"; export class SPServiceFactory { - public static createService(context: IWebPartContext, includeDelay?: boolean, delayTimeout?: number): ISPService { - if (Environment.type === EnvironmentType.Local) { - return new SPServiceMock(includeDelay, delayTimeout); - } - return new SPService(context); + public static createService(context: WebPartContext | ApplicationCustomizerContext, includeDelay?: boolean, delayTimeout?: number): ISPService { + if (Environment.type === EnvironmentType.Local) { + return new SPServiceMock(includeDelay, delayTimeout); } -} \ No newline at end of file + return new SPService(context); + } +} diff --git a/src/services/SPServiceMock.ts b/src/services/SPServiceMock.ts index 6cf8cc12f..9880a7183 100644 --- a/src/services/SPServiceMock.ts +++ b/src/services/SPServiceMock.ts @@ -1,40 +1,38 @@ import { ISPService, ILibsOptions } from "./ISPService"; import { ISPLists } from "../common/SPEntities"; -export class SPServiceMock implements ISPService { - private _includeDelay?: boolean; - private _delayTimeout?: number; - - constructor(includeDelay?: boolean, delayTimeout?: number) { - this._includeDelay = includeDelay; - this._delayTimeout = delayTimeout || 500; - } +export default class SPServiceMock implements ISPService { + private _includeDelay?: boolean; + private _delayTimeout?: number; - /** - * The mock lists to present to the local workbench - */ - private static _lists: ISPLists = { - value: [ - { Id: '8dc80f2e-0e01-43ee-b59e-fbbca2d1f35e', Title: 'Mock List One', BaseTemplate: '109' }, - { Id: '772a30d4-2d62-42da-aa48-c2a37971d693', Title: 'Mock List Two', BaseTemplate: '109' }, - { Id: '16c0d1c6-b467-4823-a37b-c308cf730366', Title: 'Mock List Three', BaseTemplate: '109' } - ] - }; - public getLibs(options?: ILibsOptions): Promise { - return new Promise(async resolve => { - if (this._includeDelay === true) { - await this.sleep(this._delayTimeout); // Simulate network load - } - resolve(SPServiceMock._lists); - }); - } - /** - * Locks the thread for the specified amount of time - * @param ms Milliseconds to wait - */ - private sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); - } -} + constructor(includeDelay?: boolean, delayTimeout?: number) { + this._includeDelay = includeDelay; + this._delayTimeout = delayTimeout || 500; + } -export default SPServiceMock; \ No newline at end of file + /** + * The mock lists to present to the local workbench + */ + private static _lists: ISPLists = { + value: [ + { Id: '8dc80f2e-0e01-43ee-b59e-fbbca2d1f35e', Title: 'Mock List One', BaseTemplate: '109' }, + { Id: '772a30d4-2d62-42da-aa48-c2a37971d693', Title: 'Mock List Two', BaseTemplate: '109' }, + { Id: '16c0d1c6-b467-4823-a37b-c308cf730366', Title: 'Mock List Three', BaseTemplate: '109' } + ] + }; + public getLibs(options?: ILibsOptions): Promise { + return new Promise(async resolve => { + if (this._includeDelay === true) { + await this.sleep(this._delayTimeout); // Simulate network load + } + resolve(SPServiceMock._lists); + }); + } + /** + * Locks the thread for the specified amount of time + * @param ms Milliseconds to wait + */ + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 38202f501..a916c98a6 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -12,7 +12,7 @@ import { SPHttpClient } from '@microsoft/sp-http'; import { SiteBreadcrumb } from '../../../SiteBreadcrumb'; import { WebPartTitle } from '../../../WebPartTitle'; import { TaxonomyPicker, IPickerTerms } from '../../../TaxonomyPicker'; - +import { ListPicker } from '../../../ListPicker'; import { IFrameDialog } from '../../../IFrameDialog'; import { Environment, EnvironmentType } from '@microsoft/sp-core-library'; @@ -72,13 +72,20 @@ export default class ControlsTest extends React.Component
+
List picker tester: + +
+
TaxonomyPicker tester: + isTermSetSelectable={false} />
iframe dialog tester: Date: Wed, 25 Apr 2018 20:34:20 +0200 Subject: [PATCH 35/35] Update changelog --- CHANGELOG.md | 1 + docs/documentation/docs/about/release-notes.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5886d14c4..b5a2b714d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **New Controls** - `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) [#64](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/64) +- `ListPicker` control got added [#34](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/34) **Fixes** diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index 5886d14c4..b5a2b714d 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -5,6 +5,7 @@ **New Controls** - `TaxonomyPicker` control got added [#22](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/22) [#63](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/63) [#64](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/64) +- `ListPicker` control got added [#34](https://github.com/SharePoint/sp-dev-fx-controls-react/issues/34) **Fixes**