Skip to content

Commit 50870ba

Browse files
committed
TermPicker fixes
1 parent 6e43ef9 commit 50870ba

File tree

9 files changed

+127
-64
lines changed

9 files changed

+127
-64
lines changed

config/tslint.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"use-named-parameter": true,
4040
"valid-typeof": true,
4141
"variable-name": false,
42-
"whitespace": false
42+
"whitespace": false,
43+
"no-debugger": true
4344
}
4445
}
45-
}
46+
}

docs/documentation/docs/beta.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Testing out a beta release ![](https://img.shields.io/npm/v/@pnp/spfx-controls-react/next.svg)
2+
3+
All you need to do for testing out a beta release of `@pnp/spfx-controls-react` is to install the dependency as follows:
4+
5+
```
6+
npm install @pnp/spfx-controls-react@next --save
7+
```
8+
9+
## Beta control documentation
10+
11+
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).
12+
13+
## Next Steps
14+
15+
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](./).
16+
17+
![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/beta)

docs/documentation/docs/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
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.
44

5-
!!! 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.
5+
!!! attention
6+
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.
67

78
## Getting started
89

docs/documentation/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pages:
2121
- FieldTitleRenderer: 'controls/fields/FieldTitleRenderer.md'
2222
- FieldUrlRenderer: 'controls/fields/FieldUrlRenderer.md'
2323
- FieldUserRenderer: 'controls/fields/FieldUserRenderer.md'
24+
- 'Beta testing': 'beta.md'
2425
- About:
2526
- 'Release notes': 'about/release-notes.md'
2627
- License: 'about/license.md'

src/controls/taxonomyPicker/ITaxonomyPicker.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface ITaxonomyPickerProps {
3131
* Limit the terms that can be picked by the Term Set name or ID
3232
*/
3333
termsetNameOrID: string;
34-
/**
34+
/**
3535
* Id of a child term in the termset where to be able to selected and search the terms from
3636
*/
3737
ancoreId?: string;
@@ -64,7 +64,7 @@ export interface ITaxonomyPickerProps {
6464
* PropertyFieldTermPickerHost state interface
6565
*/
6666
export interface ITaxonomyPickerState {
67-
67+
6868
termSetAndTerms? : ITermSet;
6969
errorMessage?: string;
7070
openPanel?: boolean;
@@ -86,7 +86,7 @@ export interface ITermParentProps extends ITermChanges {
8686
}
8787

8888
export interface ITermParentState {
89-
89+
9090
loaded?: boolean;
9191
expanded?: boolean;
9292
}

src/controls/taxonomyPicker/TaxonomyPicker.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ import { Async } from 'office-ui-fabric-react/lib/Utilities';
44
import { PrimaryButton, DefaultButton, IconButton, IButtonProps } from 'office-ui-fabric-react/lib/Button';
55
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
66
import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner';
7-
//import { IPropertyFieldTermPickerPropsInternal } from './ITermPicker';
87
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
98
import { Label } from 'office-ui-fabric-react/lib/Label';
109
import TermPicker from './TermPicker';
1110
import { BasePicker, IBasePickerProps, IPickerItemProps } from 'office-ui-fabric-react/lib/Pickers';
12-
1311
import { IPickerTerms, IPickerTerm } from './ITermPicker';
1412
import { ITaxonomyPickerProps, ITaxonomyPickerState, ITermParentProps, ITermParentState, ITermProps, ITermState } from './ITaxonomyPicker';
1513
import SPTermStorePickerService from './../../services/SPTermStorePickerService';
@@ -48,7 +46,6 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
4846

4947
appInsights.track('ReactTaxonomyPicker');
5048

51-
5249
this.state = {
5350
activeNodes: typeof this.props.initialValues !== 'undefined' ? this.props.initialValues : [],
5451
termSetAndTerms: null,
@@ -262,17 +259,14 @@ export class TaxonomyPicker extends React.Component<ITaxonomyPickerProps, ITaxon
262259
/* Show spinner in the panel while retrieving terms */
263260
this.state.loaded === false ? <Spinner type={SpinnerType.normal} /> : ''
264261
}
265-
{this.state.loaded === true &&
266-
267-
<div key={this.state.termSetAndTerms.Id} >
268-
269-
<h3>{this.state.termSetAndTerms.Name}</h3>
262+
{
263+
this.state.loaded === true && this.state.termSetAndTerms && (
264+
<div key={this.state.termSetAndTerms.Id} >
265+
<h3>{this.state.termSetAndTerms.Name}</h3>
270266
<TermParent anchorId={this.props.ancoreId} autoExpand={null} termset={this.state.termSetAndTerms} activeNodes={this.state.activeNodes} changedCallback={this.termsChanged} multiSelection={this.props.allowMultipleSelections} />
271-
</div>
272-
273-
267+
</div>
268+
)
274269
}
275-
276270
</Panel>
277271
</div >
278272
);

src/controls/taxonomyPicker/TermPicker.tsx

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,22 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
5858
* Renders the item in the picker
5959
*/
6060
protected onRenderItem(term: IPickerItemProps<IPickerTerm>) {
61-
return (<div className={styles.pickedTermRoot}
62-
key={term.index}
63-
data-selection-index={term.index}
64-
data-is-focusable={!term.disabled && true}>
65-
<span className={styles.pickedTermText}>{term.item.name}</span>
66-
{!term.disabled &&
67-
<span className={styles.pickedTermCloseIcon}
68-
onClick={term.onRemoveItem}>
69-
<i className="ms-Icon ms-Icon--Cancel" aria-hidden="true"></i>
70-
</span>
71-
}
72-
</div>);
61+
return (
62+
<div className={styles.pickedTermRoot}
63+
key={term.index}
64+
data-selection-index={term.index}
65+
data-is-focusable={!term.disabled && true}>
66+
<span className={styles.pickedTermText}>{term.item.name}</span>
67+
{
68+
!term.disabled && (
69+
<span className={styles.pickedTermCloseIcon}
70+
onClick={term.onRemoveItem}>
71+
<i className="ms-Icon ms-Icon--Cancel" aria-hidden="true"></i>
72+
</span>
73+
)
74+
}
75+
</div>
76+
);
7377
}
7478

7579
/**
@@ -84,23 +88,32 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
8488
splitPath.pop();
8589
termTitle = `${term.name} [${term.termSetName}:${splitPath.join(':')}]`;
8690
}
87-
return (<div className={styles.termSuggestion} title={termTitle}>
88-
<div>{term.name}</div>
89-
<div className={styles.termSuggestionSubTitle}> in {termParent}</div>
90-
</div>);
91+
return (
92+
<div className={styles.termSuggestion} title={termTitle}>
93+
<div>{term.name}</div>
94+
<div className={styles.termSuggestionSubTitle}> in {termParent}</div>
95+
</div>
96+
);
9197
}
9298

9399
/**
94100
* When Filter Changes a new search for suggestions
95101
*/
96-
private onFilterChanged(filterText: string, tagList: IPickerTerm[]): Promise<IPickerTerm[]> {
97-
102+
private async onFilterChanged(filterText: string, tagList: IPickerTerm[]): Promise<IPickerTerm[]> {
98103
if (filterText !== "") {
99104
let termsService = new SPTermStorePickerService(this.props.termPickerHostProps, this.props.context);
100-
let terms = termsService.searchTermsByName(filterText);
101-
return terms;
105+
let terms = await termsService.searchTermsByName(filterText);
106+
// Filter out the terms which are already set
107+
const filteredTerms = [];
108+
for (const term of terms) {
109+
if (tagList.filter(tag => tag.key === term.key).length === 0) {
110+
filteredTerms.push(term);
111+
}
112+
}
113+
return filteredTerms;
114+
} else {
115+
return Promise.resolve([]);
102116
}
103-
104117
}
105118

106119

@@ -115,14 +128,6 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
115128
* Render method
116129
*/
117130
public render(): JSX.Element {
118-
119-
// set to 1 if mutiple selections is false
120-
let itemLimit;
121-
if (!this.props.allowMultipleSelections)
122-
{
123-
itemLimit = 1;
124-
}
125-
126131
return (
127132
<div>
128133
<TermBasePicker
@@ -134,7 +139,7 @@ export default class TermPicker extends React.Component<ITermPickerProps, ITermP
134139
defaultSelectedItems={this.props.value}
135140
selectedItems={this.state.terms}
136141
onChange={this.props.onChanged}
137-
itemLimit={itemLimit}
142+
itemLimit={!this.props.allowMultipleSelections ? 1 : undefined}
138143
className={styles.termBasePicker}
139144
/>
140145
</div>

src/services/SPTermStorePickerService.ts

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,27 @@ export default class SPTermStorePickerService {
9696

9797
/**
9898
* Retrieve all terms for the given term set
99-
* @param termsetId
99+
* @param termset
100100
*/
101-
public async getAllTerms(termsetId: string): Promise<ITermSet> {
101+
public async getAllTerms(termset: string): Promise<ITermSet> {
102102
if (Environment.type === EnvironmentType.Local) {
103103
// If the running environment is local, load the data from the mock
104104
return this.getAllMockTerms();
105105
} else {
106+
let termsetId: string = termset;
107+
// Check if the provided term set property is a GUID or string
108+
if (!this.isGuid(termset)) {
109+
// Fetch the term store information
110+
const termStore = await this.getTermStores();
111+
// Get the ID of the provided term set name
112+
termsetId = this.getTermSetId(termStore, termset);
113+
if (termsetId) {
114+
termsetId = this._cleanGuid(termsetId);
115+
} else {
116+
return null;
117+
}
118+
}
119+
106120
// Request body to retrieve all terms for the given term set
107121
const data = `<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library"><Actions><ObjectPath Id="1" ObjectPathId="0" /><ObjectIdentityQuery Id="2" ObjectPathId="0" /><ObjectPath Id="4" ObjectPathId="3" /><ObjectIdentityQuery Id="5" ObjectPathId="3" /><ObjectPath Id="7" ObjectPathId="6" /><ObjectIdentityQuery Id="8" ObjectPathId="6" /><ObjectPath Id="10" ObjectPathId="9" /><Query Id="11" ObjectPathId="6"><Query SelectAllProperties="true"><Properties /></Query></Query><Query Id="12" ObjectPathId="9"><Query SelectAllProperties="false"><Properties /></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="IsRoot" SelectAll="true" /><Property Name="Labels" SelectAll="true" /><Property Name="TermsCount" SelectAll="true" /><Property Name="CustomSortOrder" SelectAll="true" /><Property Name="Id" SelectAll="true" /><Property Name="Name" SelectAll="true" /><Property Name="PathOfTerm" SelectAll="true" /><Property Name="Parent" SelectAll="true" /><Property Name="LocalCustomProperties" SelectAll="true" /></Properties></ChildItemQuery></Query></Actions><ObjectPaths><StaticMethod Id="0" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" /><Method Id="3" ParentId="0" Name="GetDefaultKeywordsTermStore" /><Method Id="6" ParentId="3" Name="GetTermSet"><Parameters><Parameter Type="Guid">${termsetId}</Parameter></Parameters></Method><Method Id="9" ParentId="6" Name="GetAllTerms" /></ObjectPaths></Request>`;
108122

@@ -118,24 +132,17 @@ export default class SPTermStorePickerService {
118132

119133
return this.context.spHttpClient.post(this.clientServiceUrl, SPHttpClient.configurations.v1, httpPostOptions).then((serviceResponse: SPHttpClientResponse) => {
120134
return serviceResponse.json().then((serviceJSONResponse: any) => {
121-
122-
console.log(serviceJSONResponse);
123-
124135
const termStoreResultTermSets: ITermSet[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermSet');
125-
console.log(termStoreResultTermSets);
126-
136+
127137
if (termStoreResultTermSets.length > 0) {
128138
var termStoreResultTermSet = termStoreResultTermSets[0];
129-
console.log("termset");
130-
console.log(termStoreResultTermSet);
131139
termStoreResultTermSet.Terms = [];
132140
// Retrieve the term collection results
133141
const termStoreResultTerms: ITerms[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermCollection');
134142
if (termStoreResultTerms.length > 0) {
135143
// Retrieve all terms
136144
let terms = termStoreResultTerms[0]._Child_Items_;
137145
// Clean the term ID and specify the path depth
138-
console.log(terms);
139146
terms = terms.map(term => {
140147
term.Id = this._cleanGuid(term.Id);
141148
term['PathDepth'] = term.PathOfTerm.split(';').length;
@@ -163,7 +170,36 @@ export default class SPTermStorePickerService {
163170

164171

165172
/**
166-
* Retrieve all terms that starts with the searchText
173+
* Get the term set ID by its name
174+
* @param termstore
175+
* @param termset
176+
*/
177+
private getTermSetId(termstore: ITermStore[], termsetName: string): string {
178+
if (termstore && termstore.length > 0 && termsetName) {
179+
// Get the first term store
180+
const ts = termstore[0];
181+
// Check if the term store contains groups
182+
if (ts.Groups && ts.Groups._Child_Items_) {
183+
for (const group of ts.Groups._Child_Items_) {
184+
// Check if the group contains term sets
185+
if (group.TermSets && group.TermSets._Child_Items_) {
186+
for (const termSet of group.TermSets._Child_Items_) {
187+
// Check if the term set is found
188+
if (termSet.Name === termsetName) {
189+
return termSet.Id;
190+
}
191+
}
192+
}
193+
}
194+
}
195+
}
196+
197+
return null;
198+
}
199+
200+
201+
/**
202+
* Retrieve all terms that starts with the searchText
167203
* @param searchText
168204
*/
169205
public searchTermsByName(searchText: string): Promise<IPickerTerm[]> {
@@ -189,8 +225,16 @@ export default class SPTermStorePickerService {
189225
this.getTermStores().then(termStore => {
190226
let TermSetId = termSet;
191227
if (!this.isGuid(termSet)) {
192-
TermSetId = this._cleanGuid(termStore[0].Groups._Child_Items_[0].TermSets._Child_Items_[0].Id);
228+
// Get the ID of the provided term set name
229+
TermSetId = this.getTermSetId(termStore, termSet);
230+
if (TermSetId) {
231+
TermSetId = this._cleanGuid(TermSetId);
232+
} else {
233+
resolve(null);
234+
return;
235+
}
193236
}
237+
194238
let data = `<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library"><Actions><ObjectPath Id="456" ObjectPathId="455" /><ObjectIdentityQuery Id="457" ObjectPathId="455" /><ObjectPath Id="459" ObjectPathId="458" /><ObjectIdentityQuery Id="460" ObjectPathId="458" /><ObjectPath Id="462" ObjectPathId="461" /><ObjectIdentityQuery Id="463" ObjectPathId="461" /><ObjectPath Id="465" ObjectPathId="464" /><SetProperty Id="466" ObjectPathId="464" Name="TermLabel"><Parameter Type="String">${searchText}</Parameter></SetProperty><SetProperty Id="467" ObjectPathId="464" Name="DefaultLabelOnly"><Parameter Type="Boolean">true</Parameter></SetProperty><SetProperty Id="468" ObjectPathId="464" Name="StringMatchOption"><Parameter Type="Number">0</Parameter></SetProperty><SetProperty Id="469" ObjectPathId="464" Name="ResultCollectionSize"><Parameter Type="Number">10</Parameter></SetProperty><SetProperty Id="470" ObjectPathId="464" Name="TrimUnavailable"><Parameter Type="Boolean">true</Parameter></SetProperty><ObjectPath Id="472" ObjectPathId="471" /><Query Id="473" ObjectPathId="471"><Query SelectAllProperties="false"><Properties /></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="IsRoot" SelectAll="true" /><Property Name="Id" SelectAll="true" /><Property Name="Name" SelectAll="true" /><Property Name="PathOfTerm" SelectAll="true" /><Property Name="TermSet" SelectAll="true" /></Properties></ChildItemQuery></Query></Actions><ObjectPaths><StaticMethod Id="455" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" /><Method Id="458" ParentId="455" Name="GetDefaultKeywordsTermStore" /><Method Id="461" ParentId="458" Name="GetTermSet"><Parameters><Parameter Type="Guid">${TermSetId}</Parameter></Parameters></Method><Constructor Id="464" TypeId="{61a1d689-2744-4ea3-a88b-c95bee9803aa}" /><Method Id="471" ParentId="461" Name="GetTerms"><Parameters><Parameter ObjectPathId="464" /></Parameters></Method></ObjectPaths></Request>`;
195239

196240
const reqHeaders = new Headers();
@@ -209,7 +253,7 @@ export default class SPTermStorePickerService {
209253
const termStoreResult: ITerms[] = serviceJSONResponse.filter(r => r['_ObjectType_'] === 'SP.Taxonomy.TermCollection');
210254
if (termStoreResult.length > 0) {
211255
// Retrieve all terms
212-
256+
213257
let terms = termStoreResult[0]._Child_Items_;
214258

215259
let returnTerms: IPickerTerm[] = [];

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
197197

198198
<div className="ms-font-m">TaxonomyPicker tester:
199199
<TaxonomyPicker
200-
allowMultipleSelections={true}
201-
termsetNameOrID="635b5bca-8c5f-4831-8bf8-a0d9d5eb75e0"
202-
// ancoreId="78d5cc84-7e29-4f9a-8636-686d819ea4b4"
200+
allowMultipleSelections={false}
201+
termsetNameOrID="Countries"
202+
// ancoreId="0ec2f948-3978-499e-9d3f-e51c4494d44c"
203203
panelTitle="Select Term"
204204
label="Taxonomy Picker"
205205
context={this.props.context}

0 commit comments

Comments
 (0)