Skip to content

Commit 2e97845

Browse files
authored
Merge pull request #1230 from pnp/1215
Fix of 1215
2 parents 83d6a9d + 78cf0ab commit 2e97845

File tree

6 files changed

+86
-97
lines changed

6 files changed

+86
-97
lines changed

src/common/utilities/SPHelper.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,14 @@ export class SPHelper {
372372
});
373373
}
374374

375+
public static isTextFieldType(fieldType?: string): boolean {
376+
if (!fieldType) {
377+
return true;
378+
}
379+
const lowercasedFieldType = fieldType.toLowerCase();
380+
return lowercasedFieldType === 'text' || lowercasedFieldType === 'note';
381+
}
382+
375383

376384
private static _updateFieldInSessionStorage(field: ISPField, context: IContext): void {
377385
let loadedViewFields: { [viewId: string]: IFields } = SPHelper._getLoadedViewFieldsFromStorage();

src/controls/dynamicForm/DynamicForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
340340
lookupListId = field["LookupList"];
341341
lookupField = field["LookupField"];
342342
if (item !== null) {
343-
defaultValue = await this._spService.getLookupValue(listId, listItemId, field.InternalName, context.pageContext.web.absoluteUrl);
343+
defaultValue = await this._spService.getLookupValue(listId, listItemId, field.InternalName, lookupField, context.pageContext.web.absoluteUrl);
344344
}
345345
else {
346346
defaultValue = [];
@@ -351,7 +351,7 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
351351
lookupListId = field["LookupList"];
352352
lookupField = field["LookupField"];
353353
if (item !== null) {
354-
defaultValue = await this._spService.getLookupValues(listId, listItemId, field.InternalName, context.pageContext.web.absoluteUrl);
354+
defaultValue = await this._spService.getLookupValues(listId, listItemId, field.InternalName, lookupField, context.pageContext.web.absoluteUrl);
355355
}
356356
else {
357357
defaultValue = [];

src/controls/listItemPicker/ListItemPicker.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IListItemPickerProps, IListItemPickerState } from ".";
88
import * as telemetry from '../../common/telemetry';
99
import isEqual from 'lodash/isEqual';
1010
import { ITag } from 'office-ui-fabric-react/lib/components/pickers/TagPicker/TagPicker.types';
11+
import { SPHelper } from '../../common/utilities/SPHelper';
1112

1213

1314
export class ListItemPicker extends React.Component<IListItemPickerProps, IListItemPickerState> {
@@ -168,7 +169,7 @@ export class ListItemPicker extends React.Component<IListItemPickerProps, IListI
168169
// Check if the list had items
169170
if (listItems.length > 0) {
170171
for (const item of listItems) {
171-
arrayItems.push({ key: item[keyColumn], name: item[columnInternalName] });
172+
arrayItems.push({ key: item[keyColumn], name: SPHelper.isTextFieldType(field.TypeAsString === 'Calculated' ? field.ResultType : field.TypeAsString) ? item[columnInternalName] : item.FieldValuesAsText[columnInternalName] });
172173
}
173174
}
174175
return arrayItems;

src/services/SPService.ts

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ import { ISPService, ILibsOptions, LibsOrderBy, IFieldsOptions, FieldsOrderBy }
22
import { ISPField, ISPList, ISPLists, IUploadImageResult } from "../common/SPEntities";
33
import { BaseComponentContext } from '@microsoft/sp-component-base';
44
import { SPHttpClient, ISPHttpClientOptions } from "@microsoft/sp-http";
5-
import { urlCombine } from "../common/utilities";
5+
import { SPHelper, urlCombine } from "../common/utilities";
66
import filter from 'lodash/filter';
77
import find from 'lodash/find';
88

9+
interface ICachedListItems {
10+
items: any[];
11+
expiration: number;
12+
}
13+
914
export default class SPService implements ISPService {
1015

1116
private _webAbsoluteUrl: string;
17+
private _cachedListItems: Map<string, ICachedListItems> = new Map<string, ICachedListItems>();
18+
1219

1320
constructor(private _context: BaseComponentContext, webAbsoluteUrl?: string) {
1421
this._webAbsoluteUrl = webAbsoluteUrl ? webAbsoluteUrl : this._context.pageContext.web.absoluteUrl;
@@ -146,13 +153,24 @@ export default class SPService implements ISPService {
146153
/**
147154
* Get List Items
148155
*/
149-
public async getListItems(filterText: string, listId: string, internalColumnName: string, field: ISPField | undefined, keyInternalColumnName?: string, webUrl?: string, filterString?: string, substringSearch: boolean = false, orderBy?: string): Promise<any[]> {
156+
public async getListItems(
157+
filterText: string,
158+
listId: string,
159+
internalColumnName: string,
160+
field: ISPField | undefined,
161+
keyInternalColumnName?: string,
162+
webUrl?: string,
163+
filterString?: string,
164+
substringSearch: boolean = false,
165+
orderBy?: string,
166+
cacheInterval: number = 1): Promise<any[]> {
150167
let returnItems: any[];
151168
const webAbsoluteUrl = !webUrl ? this._webAbsoluteUrl : webUrl;
152169
let apiUrl = '';
153170
let isPost = false;
171+
let processItems: ((items: any[]) => any[]) | undefined;
154172

155-
if (field && field.TypeAsString === 'Calculated') { // for calculated fields we need to use CAML query
173+
if (field && field.TypeAsString === 'Calculated' && SPHelper.isTextFieldType(field.ResultType)) { // for calculated fields we need to use CAML query
156174
let orderByStr = '';
157175

158176
if (orderBy) {
@@ -169,19 +187,40 @@ export default class SPService implements ISPService {
169187
apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/GetItems(query=@v1)?$select=${keyInternalColumnName || 'Id'},${internalColumnName}&@v1=${JSON.stringify({ ViewXml: camlQuery })}`;
170188
isPost = true;
171189
}
172-
else {
190+
else if (SPHelper.isTextFieldType(field.TypeAsString)) {
173191
const filterStr = substringSearch ? // JJ - 20200613 - find by substring as an option
174192
`${filterText ? `substringof('${encodeURIComponent(filterText.replace("'", "''"))}',${internalColumnName})` : ''}${filterString ? (filterText ? ' and ' : '') + filterString : ''}`
175193
: `${filterText ? `startswith(${internalColumnName},'${encodeURIComponent(filterText.replace("'", "''"))}')` : ''}${filterString ? (filterText ? ' and ' : '') + filterString : ''}`; //string = filterList ? `and ${filterList}` : '';
176194
apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items?$select=${keyInternalColumnName || 'Id'},${internalColumnName}&$filter=${filterStr}&$orderby=${orderBy}`;
177195
}
196+
else { // we need to get FieldValuesAsText and cache them
197+
const mapKey = `${webAbsoluteUrl}##${listId}##${internalColumnName}##${keyInternalColumnName || 'Id'}`;
198+
const cachedItems = this._cachedListItems.get(mapKey);
199+
200+
if (cachedItems && cachedItems.expiration < Date.now()) {
201+
return this._filterListItemsFieldValuesAsText(cachedItems.items, internalColumnName, filterText, substringSearch);
202+
}
203+
204+
apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items?$select=${keyInternalColumnName || 'Id'},${internalColumnName},FieldValuesAsText/${internalColumnName}&$expand=FieldValuesAsText&$orderby=${orderBy}${filterString ? '&$filter=' + filterString : ''}`;
205+
isPost = false;
206+
207+
processItems = (items: any[]) => {
208+
209+
this._cachedListItems.set(mapKey, {
210+
items,
211+
expiration: Date.now() + cacheInterval * 60 * 1000
212+
});
213+
214+
return this._filterListItemsFieldValuesAsText(items, internalColumnName, filterText, substringSearch);
215+
};
216+
}
178217

179218
try {
180219
const data = isPost ? await this._context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, {}) : await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
181220
if (data.ok) {
182221
const results = await data.json();
183222
if (results && results.value && results.value.length > 0) {
184-
return results.value;
223+
return processItems ? processItems(results.value) : results.value;
185224
}
186225
}
187226

@@ -191,8 +230,6 @@ export default class SPService implements ISPService {
191230
}
192231
}
193232

194-
195-
196233
/**
197234
* Gets list items for list item picker
198235
* @param filterText
@@ -430,16 +467,16 @@ export default class SPService implements ISPService {
430467
return;
431468
}
432469

433-
public async getLookupValue(listId: string, listItemID: number, fieldName: string, webUrl?: string): Promise<any[]> {
470+
public async getLookupValue(listId: string, listItemID: number, fieldName: string, lookupFieldName: string | undefined, webUrl?: string): Promise<any[]> {
434471
try {
435472
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
436-
let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(${listItemID})/?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/ID,${fieldName}/Title&$expand=${fieldName}`;
473+
let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(${listItemID})/?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/ID,${fieldName}/${lookupFieldName || 'Title'}&$expand=${fieldName}`;
437474

438475
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
439476
if (data.ok) {
440477
const result = await data.json();
441478
if (result && result[fieldName]) {
442-
return [{ key: result[fieldName].ID, name: result[fieldName].Title }];
479+
return [{ key: result[fieldName].ID, name: result[fieldName][lookupFieldName || 'Title'] }];
443480
}
444481
}
445482

@@ -450,18 +487,18 @@ export default class SPService implements ISPService {
450487
}
451488
}
452489

453-
public async getLookupValues(listId: string, listItemID: number, fieldName: string, webUrl?: string): Promise<any[]> {
490+
public async getLookupValues(listId: string, listItemID: number, fieldName: string, lookupFieldName: string | undefined, webUrl?: string): Promise<any[]> {
454491
try {
455492
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
456-
let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(${listItemID})?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/ID,${fieldName}/Title&$expand=${fieldName}`;
493+
let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(${listItemID})?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/ID,${fieldName}/${lookupFieldName || 'Title'}&$expand=${fieldName}`;
457494

458495
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
459496
if (data.ok) {
460497
const result = await data.json();
461498
if (result && result[fieldName]) {
462499
let lookups = [];
463500
result[fieldName].forEach(element => {
464-
lookups.push({ key: element.ID, name: element.Title });
501+
lookups.push({ key: element.ID, name: element[lookupFieldName || 'Title'] });
465502
});
466503
return lookups;
467504
}
@@ -600,4 +637,22 @@ export default class SPService implements ISPService {
600637

601638
return result;
602639
}
640+
641+
private _filterListItemsFieldValuesAsText(items: any[], internalColumnName: string, filterText: string | undefined, substringSearch: boolean): any[] {
642+
const lowercasedFilterText = filterText.toLowerCase();
643+
644+
return items.filter(i => {
645+
let fieldValue = i.FieldValuesAsText[internalColumnName];
646+
if (!fieldValue) {
647+
return false;
648+
}
649+
fieldValue = fieldValue.toLowerCase();
650+
651+
if (!filterText) {
652+
return true;
653+
}
654+
655+
return substringSearch ? fieldValue.indexOf(lowercasedFilterText) > -1 : fieldValue.startsWith(lowercasedFilterText);
656+
});
657+
}
603658
}

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
888888
<div className={styles.controlsTest}>
889889
<div className="ms-font-m">
890890
{/* Change the list Id and list item id before you start to test this control */}
891-
{/* <DynamicForm context={this.props.context} listId={"3071c058-549f-461d-9d73-8b9a52049a80"} listItemId={1} onCancelled={() => { console.log('Cancelled'); }} onSubmitted={async (listItem) => { let itemdata = await listItem.get(); console.log(itemdata["ID"]); }}></DynamicForm> */}
891+
<DynamicForm context={this.props.context} listId={"b1416fca-dc77-4198-a082-62a7657dcfa9"} onCancelled={() => { console.log('Cancelled'); }} onSubmitted={async (listItem) => { let itemdata = await listItem.get(); console.log(itemdata["ID"]); }}></DynamicForm>
892892
</div>
893893
<WebPartTitle displayMode={this.props.displayMode}
894894
title={this.props.title}
@@ -1466,10 +1466,10 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
14661466

14671467
<div className="ms-font-m">List Item picker list data tester:
14681468

1469-
<ListItemPicker listId={'76a8231b-35b6-4703-b1f4-5d03d3dfb1ca'}
1470-
columnInternalName="Title"
1469+
<ListItemPicker listId={'b1416fca-dc77-4198-a082-62a7657dcfa9'}
1470+
columnInternalName="DateAndTime"
14711471
keyColumnInternalName="Id"
1472-
filter={"Title eq 'SPFx'"}
1472+
// filter={"Title eq 'SPFx'"}
14731473
orderBy={'Title desc'}
14741474
itemLimit={5}
14751475
context={this.props.context}

src/webparts/controlsTest/components/ControlsTest_SingleComponent.tsx

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -464,87 +464,12 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
464464
}];
465465

466466
const onContextualMenuClick = (id: string) => {
467-
this.setState({termPanelIsOpen: true, actionTermId: id});
467+
this.setState({ termPanelIsOpen: true, actionTermId: id });
468468
};
469469

470470
return (
471471
<div>
472-
<ModernTaxonomyPicker
473-
allowMultipleSelections={true}
474-
termSetId={"36d21c3f-b83b-4acc-a223-4df6fa8e946d"}
475-
panelTitle="Panel title"
476-
label={"Field title"}
477-
context={this.props.context}
478-
required={false}
479-
// initialValues={[{labels: [{name: "Subprocess A1", isDefault: true, languageTag: "en-US"}], id: "29eced8f-cf08-454b-bd9e-6443bc0a0f5e", childrenCount: 0, createdDateTime: "", lastModifiedDateTime: "", descriptions: [], customSortOrder: [], properties: [], localProperties: [], isDeprecated: false, isAvailableForTagging: [], topicRequested: false}]}
480-
// onChange={(values) => alert(values.map((value) => `${value?.id} - ${value?.labels[0].name}`).join("\n"))}
481-
disabled={false}
482-
customPanelWidth={700}
483-
isLightDismiss={false}
484-
isBlocking={false}
485-
onRenderActionButton={(termStoreInfo: ITermStoreInfo, termSetInfo: ITermSetInfo, termInfo?: ITermInfo) => {
486-
const menuIcon: IIconProps = { iconName: 'MoreVertical', "aria-label": "More actions", style: { fontSize: "medium" } };
487-
if (termInfo) {
488-
const menuProps: IContextualMenuProps = {
489-
items: [
490-
{
491-
key: 'addTerm',
492-
text: 'Add Term',
493-
iconProps: { iconName: 'Tag' },
494-
onClick: () => onContextualMenuClick(termInfo.id)
495-
},
496-
{
497-
key: 'deleteTerm',
498-
text: 'Delete term',
499-
iconProps: { iconName: 'Untag' },
500-
onClick: () => onContextualMenuClick(termInfo.id)
501-
},
502-
],
503-
};
504-
505-
return (
506-
<IconButton
507-
menuProps={menuProps}
508-
menuIconProps={menuIcon}
509-
style={this.state.clickedActionTerm && this.state.clickedActionTerm.id === termInfo.id ? {opacity: 1} : null}
510-
onMenuClick={(ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>, button?: IButtonProps) => {
511-
this.setState({clickedActionTerm: termInfo});
512-
}}
513-
onAfterMenuDismiss={() => this.setState({clickedActionTerm: null})}
514-
/>
515-
);
516-
}
517-
else {
518-
const menuProps: IContextualMenuProps = {
519-
items: [
520-
{
521-
key: 'addTerm',
522-
text: 'Add term',
523-
iconProps: { iconName: 'Tag' },
524-
onClick: () => onContextualMenuClick(termSetInfo.id)
525-
},
526-
],
527-
};
528-
return (
529-
<IconButton
530-
menuProps={menuProps}
531-
menuIconProps={menuIcon}
532-
style={{opacity: 1}}
533-
/>
534-
);
535-
}
536-
}}
537-
/>
538-
<Panel
539-
isOpen={this.state.termPanelIsOpen}
540-
onDismiss={() => this.setState({termPanelIsOpen: false, actionTermId: null})}
541-
hasCloseButton={true}
542-
isLightDismiss={false}
543-
isBlocking={false}
544-
545-
>
546-
<span>{this.state.actionTermId && this.state.actionTermId}</span>
547-
</Panel>
472+
<DynamicForm context={this.props.context} listId={"b1416fca-dc77-4198-a082-62a7657dcfa9"} listItemId={1} onCancelled={() => { console.log('Cancelled'); }} onSubmitted={async (listItem) => { let itemdata = await listItem.get(); console.log(itemdata["ID"]); }}></DynamicForm>
548473
</div>
549474
);
550475
}

0 commit comments

Comments
 (0)