Merge pull request #1838 from albegut/master
This commit is contained in:
commit
40e55bc5cd
|
@ -3,7 +3,8 @@
|
|||
## Summary
|
||||
|
||||
This list search web part allows the user to show data from lists or libraries. The web part can be used to (for more details see images below):
|
||||
* [Show merged items from diferents lists/libraries](#merge-items-from-different-listslibraries)
|
||||
|
||||
* [Show merged items from different lists/libraries](#merge-items-from-different-listslibraries)
|
||||
* [Open item data in modal window (same data shown in the table)](#merge-items-from-different-listslibraries)
|
||||
* [Select render by field type](#select-render-of-the-selected-fields)
|
||||
* [Open item detail in modal window (it allows to select the fields to show by list)](#open-selected-item-with-selected-properties)
|
||||
|
@ -13,6 +14,7 @@ This list search web part allows the user to show data from lists or libraries.
|
|||
* [Redirect to url](#redirect-to-url-depends-on-selected-item)
|
||||
|
||||
* Other useful functionalities:
|
||||
* List item modern audience support
|
||||
* General filter - the user can select which columns are filtered and which not
|
||||
* Column filter on each column
|
||||
* Item limit to show
|
||||
|
@ -22,39 +24,38 @@ This list search web part allows the user to show data from lists or libraries.
|
|||
* Get section color
|
||||
* Show item count with custom message
|
||||
|
||||
#### Merge items from different lists/libraries
|
||||
### Merge items from different lists/libraries
|
||||
|
||||
![Merge items from different lists/libraries](assets/differentSources.gif)
|
||||
|
||||
#### Select render of the selected fields
|
||||
### Select render of the selected fields
|
||||
|
||||
![Select render of the selected fields](assets/selectFieldRenderType.gif)
|
||||
|
||||
#### Open documents in modal window
|
||||
### Open documents in modal window
|
||||
|
||||
![Open documents in modal window](assets/docInModal.gif)
|
||||
|
||||
#### Open documents in new tab
|
||||
### Open documents in new tab
|
||||
|
||||
![Open documents in new tab](assets/docInNewTab.gif)
|
||||
|
||||
#### Use of dynamic data
|
||||
### Use of dynamic data
|
||||
|
||||
![Use of dynamic data](assets/dynamicData.gif)
|
||||
|
||||
#### Open selected item with same data
|
||||
### Open selected item with same data
|
||||
|
||||
![Open selected item with same data](assets/itemCurrentData.gif)
|
||||
|
||||
#### Open selected item with selected properties
|
||||
### Open selected item with selected properties
|
||||
|
||||
![Open selected item with selected properties](assets/itemSelectedData.gif)
|
||||
|
||||
#### Redirect to url depends on selected item
|
||||
### Redirect to url depends on selected item
|
||||
|
||||
![Redirect to url depends on selected item](assets/redirectToUrl.gif)
|
||||
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||
|
@ -63,7 +64,6 @@ This list search web part allows the user to show data from lists or libraries.
|
|||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
|
||||
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||
|
@ -73,19 +73,14 @@ This list search web part allows the user to show data from lists or libraries.
|
|||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-list-search | Alberto Gutiérrez ([@albertogperez](https://twitter.com/albertogperez))
|
||||
react-list-search | [Alberto Gutiérrez](https://github.com/albegut) ([@albertogperez](https://twitter.com/albertogperez))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|December 20, 2020|Initial release
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
1.0.0|December 20, 2020|Initial release
|
||||
1.1.0|April 25, 2021|List item modern audience support
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
|
@ -103,6 +98,7 @@ Version|Date|Comments
|
|||
|
||||
* Download `.sppkg` files from `sppkg` folder
|
||||
* Upload files to **App Catalog**
|
||||
* Approve the API permissions in the new SP admin center (only needed if you are going to enable list item modern audience)
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -112,8 +108,22 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
|||
* Using [PnP Js](https://pnp.github.io/pnpjs) to retrieve SharePoint data
|
||||
* Using [PnP Js](https://pnp.github.io/pnpjs/odata/caching) to cache SharePoint data
|
||||
* Connection between SharePoint Framework components using dynamic data
|
||||
* [Support of section backgrounds color ](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/supporting-section-backgrounds)
|
||||
* [Support of section backgrounds color](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/supporting-section-backgrounds)
|
||||
* [Custom property pane control](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/build-custom-property-pane-controls)
|
||||
* Use [react-js-pagination](https://www.npmjs.com/package/react-js-pagination) library
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
## Support
|
||||
|
||||
We do not support samples, but we do use GitHub to track issues and constantly want to improve these samples.
|
||||
|
||||
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=react-list-search&authors=@albegut&title=react-list-search%20-%20).
|
||||
|
||||
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=react-list-search&authors=@albegut&title=react-list-search%20-%20).
|
||||
|
||||
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-list-search&authors=@albegut&title=react-list-search%20-%20).
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-list-search" />
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
"longDescription": [
|
||||
"This list search web part allows the user to show data from lists or libraries."
|
||||
],
|
||||
"created": "2020-12-20",
|
||||
"modified": "2020-12-20",
|
||||
"creationDateTime": "2020-12-20",
|
||||
"updateDateTime": "2021-04-25",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
|
@ -100,4 +100,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "list-search-webpart",
|
||||
"id": "8277f088-9c30-4f95-9c15-9c18a9d40a26",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
|
@ -13,7 +13,25 @@
|
|||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
}
|
||||
},
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadWrite.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Directory.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Directory.ReadWrite.All"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/list-search-webpart.sppkg"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,14 +22,15 @@
|
|||
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||
"@microsoft/sp-property-pane": "1.11.0",
|
||||
"@microsoft/sp-webpart-base": "1.11.0",
|
||||
"@pnp/graph": "2.4.0",
|
||||
"@pnp/sp": "2.0.8",
|
||||
"@pnp/spfx-controls-react": "1.19.0",
|
||||
"@pnp/spfx-property-controls": "1.19.0",
|
||||
"react-js-pagination": "3.0.3",
|
||||
"react-xml-parser": "1.1.6",
|
||||
"@pnp/spfx-controls-react": "2.4.0",
|
||||
"@pnp/spfx-property-controls": "2.5.0",
|
||||
"office-ui-fabric-react": "7.155.3",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5",
|
||||
"office-ui-fabric-react": "7.155.3"
|
||||
"react-js-pagination": "3.0.3",
|
||||
"react-xml-parser": "1.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.11.0",
|
||||
|
|
Binary file not shown.
|
@ -31,6 +31,7 @@ import { IDynamicItem } from './model/IDynamicItem';
|
|||
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
|
||||
import { SharePointFieldTypes, SharePointType } from './model/ISharePointFieldTypes';
|
||||
import { IModalType } from './model/IModalType';
|
||||
import { find, has } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
|
||||
|
||||
|
@ -76,6 +77,7 @@ export interface IListSearchWebPartProps {
|
|||
initialQueryOption: "simpleText" | "dynamicData";
|
||||
dynamicQueryText: DynamicProperty<string>;
|
||||
initialQueryText: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default class ListSearchWebPart extends BaseClientSideWebPart<IListSearchWebPartProps> implements IDynamicDataCallables {
|
||||
|
@ -294,7 +296,12 @@ export default class ListSearchWebPart extends BaseClientSideWebPart<IListSearch
|
|||
AnyCamlQuery: (this.properties.listsCollectionData.findIndex(listConfig => !this.isEmpty(listConfig.Query) || !this.isEmpty(listConfig.ListView)) > -1),
|
||||
groupByFieldType: this.properties.groupByFieldType,
|
||||
CacheType: this.properties.CacheType,
|
||||
generalFilterText: queryText
|
||||
generalFilterText: queryText,
|
||||
title: this.properties.title,
|
||||
updateTitle: (value: string) => {
|
||||
this.properties.title = value;
|
||||
},
|
||||
displayMode: this.displayMode,
|
||||
}
|
||||
);
|
||||
renderElement = element;
|
||||
|
@ -574,7 +581,7 @@ export default class ListSearchWebPart extends BaseClientSideWebPart<IListSearch
|
|||
]
|
||||
}) : emptyProperty;
|
||||
|
||||
let GeneralFilterInitialQueryText = this.properties.initialQueryEnabled && this.properties.initialQueryOption === "simpleText" ? PropertyPaneTextField('initialQueryText', {
|
||||
let GeneralFilterInitialQueryText = this.properties.initialQueryEnabled && this.properties.initialQueryOption === "simpleText" ? PropertyPaneTextField('initialQueryText', {
|
||||
label: strings.GeneralFilterInitialQueryTextValue,
|
||||
}) : emptyProperty;
|
||||
|
||||
|
@ -866,6 +873,11 @@ export default class ListSearchWebPart extends BaseClientSideWebPart<IListSearch
|
|||
title: strings.CollectionDataListCamlQueryTitle,
|
||||
placeholder: strings.CollectionDataListCamlQueryPlaceHolder,
|
||||
type: CustomCollectionFieldType.string,
|
||||
},
|
||||
{
|
||||
id: "AudienceEnabled",
|
||||
title: strings.ModerAudienceEnabledTitle,
|
||||
type: CustomCollectionFieldType.boolean,
|
||||
}
|
||||
],
|
||||
disabled: !this.properties.sites || this.properties.sites.length == 0,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
|||
import { SharePointType } from "../model/ISharePointFieldTypes";
|
||||
import { IModalType } from "../model/IModalType";
|
||||
import { IDynamicItem } from "../model/IDynamicItem";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
|
||||
|
||||
export interface IListSearchProps {
|
||||
|
@ -47,4 +48,7 @@ export interface IListSearchProps {
|
|||
AnyCamlQuery: boolean;
|
||||
CacheType: "session" | "local";
|
||||
generalFilterText: string;
|
||||
title: string;
|
||||
updateTitle: (title: string) => void;
|
||||
displayMode: DisplayMode;
|
||||
}
|
||||
|
|
|
@ -4,21 +4,22 @@ import * as strings from 'ListSearchWebPartStrings';
|
|||
import ListService from '../services/ListService';
|
||||
import IGroupedItems, { IListSearchState, IColumnFilter } from './IListSearchState';
|
||||
import { IListSearchProps } from './IListSearchProps';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import {
|
||||
DetailsList,
|
||||
IColumn,
|
||||
IDetailsFooterProps,
|
||||
IDetailsRowBaseProps,
|
||||
DetailsRow,
|
||||
SelectionMode,
|
||||
IGroup
|
||||
IGroup,
|
||||
DetailsHeader,
|
||||
DetailsListLayoutMode,
|
||||
} from 'office-ui-fabric-react/lib/DetailsList';
|
||||
import {
|
||||
getTheme,
|
||||
IconButton,
|
||||
MessageBar,
|
||||
MessageBarType
|
||||
MessageBarType,
|
||||
ShimmeredDetailsList
|
||||
} from 'office-ui-fabric-react';
|
||||
import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox';
|
||||
import Pagination from "react-js-pagination";
|
||||
|
@ -41,9 +42,11 @@ import IUserField from '../model/IUserField';
|
|||
import IUrlField from '../model/IUrlField';
|
||||
import { IFrameDialog } from "@pnp/spfx-controls-react/lib/IFrameDialog";
|
||||
import { IModalType } from '../model/IModalType';
|
||||
import { groupBy, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
import { find, groupBy, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
import IResult from '../model/IResult';
|
||||
import { IListSearchListQuery, IMapQuery } from '../model/IMapQuery';
|
||||
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
||||
import GraphService from '../services/GraphService';
|
||||
|
||||
|
||||
const LOG_SOURCE = "IListdSearchWebPart";
|
||||
|
@ -52,6 +55,8 @@ const filterIcon: IIconProps = { iconName: 'Filter' };
|
|||
export default class IListdSearchWebPart extends React.Component<IListSearchProps, IListSearchState> {
|
||||
private groups: IGroup[];
|
||||
private keymapQuerys: IMapQuery = {};
|
||||
private _graphService: GraphService;
|
||||
|
||||
constructor(props: IListSearchProps, state: IListSearchState) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -67,11 +72,12 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
isModalLoading: false,
|
||||
selectedItem: null,
|
||||
completeModalItemData: null,
|
||||
columns: [],
|
||||
columns: this.AddColumnsToDisplay(),
|
||||
groupedItems: []
|
||||
};
|
||||
this.GetJSXElementByType = this.GetJSXElementByType.bind(this);
|
||||
this._renderItemColumn = this._renderItemColumn.bind(this);
|
||||
this._graphService = new GraphService(this.props.Context);
|
||||
|
||||
}
|
||||
|
||||
|
@ -109,7 +115,6 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
result = result.slice(0, this.props.ItemLimit);
|
||||
}
|
||||
|
||||
let columns = this.AddColumnsToDisplay();
|
||||
let groupedItems = [];
|
||||
if (this.props.groupByField) {
|
||||
groupedItems = this._groupBy(result, this.props.groupByField, this.props.groupByFieldType);
|
||||
|
@ -120,7 +125,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
filteredItems = this.filterListItemsByGeneralFilter(this.props.generalFilterText, false, false, result, filteredItems);
|
||||
}
|
||||
|
||||
this.setState({ items: result, filterItems: filteredItems, isLoading: false, columns, groupedItems });
|
||||
this.setState({ items: result, filterItems: filteredItems, isLoading: false, groupedItems });
|
||||
} catch (error) {
|
||||
this.SetError(error, "getData");
|
||||
}
|
||||
|
@ -134,7 +139,13 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
let listService: ListService = new ListService(site, this.props.UseCache, this.props.minutesToCache, this.props.CacheType);
|
||||
let siteProperties = this.props.Sites.filter(siteInformation => siteInformation.url === site);
|
||||
Object.keys(this.keymapQuerys[site]).map(listQuery => {
|
||||
itemPromise.push(listService.getListItems(this.keymapQuerys[site][listQuery], this.props.ListNameTitle, this.props.SiteNameTitle, siteProperties[0][this.props.SiteNamePropertyToShow], this.props.ItemLimit));
|
||||
itemPromise.push(listService.getListItems(
|
||||
this.keymapQuerys[site][listQuery],
|
||||
this.props.ListNameTitle,
|
||||
this.props.SiteNameTitle,
|
||||
siteProperties[0][this.props.SiteNamePropertyToShow],
|
||||
this.props.ItemLimit,
|
||||
this._graphService));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -189,16 +200,26 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
}
|
||||
}
|
||||
else {
|
||||
let listQueryInfo = this.props.listsCollectionData.filter(list => list.SiteCollectionSource == item.SiteCollectionSource && list.ListSourceField == item.ListSourceField);
|
||||
|
||||
let newQueryListItem: IListSearchListQuery = { list: { Id: item.ListSourceField, Title: item.ListSourceFieldName }, fields: [{ originalField: item.SourceField, newField: item.TargetField, fieldType: item.SPFieldType }], camlQuery: listQueryInfo.length > 0 && listQueryInfo[0].Query, viewName: listQueryInfo.length > 0 && listQueryInfo[0].ListView };
|
||||
let listQueryInfo = find(this.props.listsCollectionData, list => list.SiteCollectionSource == item.SiteCollectionSource && list.ListSourceField == item.ListSourceField);
|
||||
let newQueryListItem: IListSearchListQuery = {
|
||||
list: { Id: item.ListSourceField, Title: item.ListSourceFieldName },
|
||||
audienceEnabled: listQueryInfo.AudienceEnabled,
|
||||
fields: [{ originalField: item.SourceField, newField: item.TargetField, fieldType: item.SPFieldType }],
|
||||
camlQuery: listQueryInfo && listQueryInfo.Query,
|
||||
viewName: listQueryInfo && listQueryInfo.ListView
|
||||
};
|
||||
this.keymapQuerys[item.SiteCollectionSource][item.ListSourceField] = newQueryListItem;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let listQueryInfo = this.props.listsCollectionData.filter(list => list.SiteCollectionSource == item.SiteCollectionSource && list.ListSourceField == item.ListSourceField);
|
||||
|
||||
let newQueryListItem: IListSearchListQuery = { list: { Id: item.ListSourceField, Title: item.ListSourceFieldName }, fields: [{ originalField: item.SourceField, newField: item.TargetField, fieldType: item.SPFieldType }], camlQuery: listQueryInfo.length > 0 && listQueryInfo[0].Query, viewName: listQueryInfo.length > 0 && listQueryInfo[0].ListView };
|
||||
let listQueryInfo = find(this.props.listsCollectionData, list => list.SiteCollectionSource == item.SiteCollectionSource && list.ListSourceField == item.ListSourceField);
|
||||
let newQueryListItem: IListSearchListQuery = {
|
||||
list: { Id: item.ListSourceField, Title: item.ListSourceFieldName },
|
||||
audienceEnabled: listQueryInfo.AudienceEnabled,
|
||||
fields: [{ originalField: item.SourceField, newField: item.TargetField, fieldType: item.SPFieldType }],
|
||||
camlQuery: listQueryInfo && listQueryInfo.Query,
|
||||
viewName: listQueryInfo && listQueryInfo.ListView
|
||||
};
|
||||
this.keymapQuerys[item.SiteCollectionSource] = [];
|
||||
this.keymapQuerys[item.SiteCollectionSource][item.ListSourceField] = newQueryListItem;
|
||||
}
|
||||
|
@ -300,27 +321,38 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
}
|
||||
|
||||
|
||||
private _onRenderDetails(detailsFooterProps: IDetailsFooterProps): JSX.Element {
|
||||
let _renderDetailsFooterItemColumn: IDetailsRowBaseProps['onRenderItemColumn'] = (item, index, column) => {
|
||||
let filter: IColumnFilter = this.state.columnFilters.find(colFilter => colFilter.columnName == column.name);
|
||||
if (this.props.IndividualColumnFilter && column.data != SharePointType.FileIcon) {
|
||||
return (
|
||||
<SearchBox placeholder={column.name} iconProps={filterIcon} value={filter ? filter.filterToApply : ""}
|
||||
underlined={true} onChange={(ev, value) => this.filterColumnListItems(column.name, value, column.data)} onClear={(ev) => this.filterColumnListItems(column.name, "", SharePointType.Text)} />
|
||||
);
|
||||
private _onRenderDetails(detailsFooterProps: IDetailsFooterProps, showSearchBox: boolean, isHeader: boolean): JSX.Element {
|
||||
if (this.props.IndividualColumnFilter) {
|
||||
let _renderDetailsFooterItemColumn: IDetailsRowBaseProps['onRenderItemColumn'] = (item, index, column) => {
|
||||
let filter: IColumnFilter = this.state.columnFilters.find(colFilter => colFilter.columnName == column.name);
|
||||
if (this.props.IndividualColumnFilter && showSearchBox && column.data != SharePointType.FileIcon) {
|
||||
return (
|
||||
<SearchBox placeholder={column.name} iconProps={filterIcon} value={filter ? filter.filterToApply : ""}
|
||||
underlined={true} onChange={(ev, value) => this.filterColumnListItems(column.name, value, column.data)} onClear={(ev) => this.filterColumnListItems(column.name, "", SharePointType.Text)} />
|
||||
);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DetailsRow
|
||||
{...detailsFooterProps}
|
||||
item={{}}
|
||||
itemIndex={-1}
|
||||
onRenderItemColumn={_renderDetailsFooterItemColumn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
else {
|
||||
if (isHeader) {
|
||||
return <DetailsHeader {...detailsFooterProps} layoutMode={DetailsListLayoutMode.justified} styles={{ root: { backgroundColor: 'transparent' } }} />;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DetailsRow
|
||||
{...detailsFooterProps}
|
||||
item={{}}
|
||||
itemIndex={-1}
|
||||
onRenderItemColumn={_renderDetailsFooterItemColumn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handlePageChange(pageNumber) {
|
||||
|
@ -371,6 +403,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
|
||||
let completeItemQueryOptions: IListSearchListQuery = {
|
||||
list: item.List,
|
||||
audienceEnabled: true,
|
||||
fields: this.props.completeModalFields && this.props.completeModalFields.filter(field => field.SiteCollectionSource == item.SiteUrl &&
|
||||
field.ListSourceField == item.List.Id).map(field => { return { originalField: field.SourceField, newField: field.TargetField, fieldType: field.SPFieldType }; })
|
||||
};
|
||||
|
@ -540,21 +573,20 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
return this.props.clickEnabled ?
|
||||
this.props.oneClickOption ?
|
||||
<div onClick={() => this._onItemInvoked(detailrow.item)}>
|
||||
{defaultRender({ ...detailrow, styles: { root: { cursor: 'pointer' } } })}
|
||||
{defaultRender({ ...detailrow, styles: { root: { cursor: 'pointer', backgroundColor: 'transparent' } } })}
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
{defaultRender({ ...detailrow, styles: { root: { cursor: 'pointer' } } })}
|
||||
{defaultRender({ ...detailrow, styles: { root: { cursor: 'pointer', backgroundColor: 'transparent' } } })}
|
||||
</>
|
||||
:
|
||||
<>
|
||||
{defaultRender({ ...detailrow })}
|
||||
{defaultRender({ ...detailrow, styles: { root: { backgroundColor: 'transparent' } } })}
|
||||
</>;
|
||||
}
|
||||
|
||||
private _renderItemColumn(item: any, index: number, column: IColumn): JSX.Element {
|
||||
let result = this.GetJSXElementByType(item, column.fieldName, column.data);
|
||||
return result;
|
||||
return this.GetJSXElementByType(item, column.fieldName, column.data);
|
||||
}
|
||||
|
||||
private GetModalBodyRenderByFieldType(item: any, propertyName: string, fieldType: SharePointType): JSX.Element {
|
||||
|
@ -649,7 +681,8 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
|
||||
private GetJSXElementByType(item: any, fieldName: string, fieldType: SharePointType, ommitCamlQuery: boolean = false): JSX.Element {
|
||||
const value: any = this.GetItemValueFieldByFieldType(item, fieldName, fieldType, ommitCamlQuery);
|
||||
let result;
|
||||
const { semanticColors }: IReadonlyTheme = this.props.themeVariant;
|
||||
let result: JSX.Element = <span></span>;
|
||||
switch (fieldType) {
|
||||
case SharePointType.FileIcon:
|
||||
{
|
||||
|
@ -659,7 +692,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
case SharePointType.User:
|
||||
{
|
||||
if (this.props.AnyCamlQuery && !ommitCamlQuery) {
|
||||
result = <span>{value}</span>;
|
||||
result = <span style={{ color: semanticColors.bodyText }}>{value}</span>;
|
||||
}
|
||||
else {
|
||||
if (value && value.Name) {
|
||||
|
@ -684,10 +717,10 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
if (this.props.AnyCamlQuery && !ommitCamlQuery && value && value.length > 0) {
|
||||
result = <span>{value.map((val, index) => {
|
||||
if (index + 1 == value.length) {
|
||||
return <span>{val}</span>;
|
||||
return <span style={{ color: semanticColors.bodyText }}>{val}</span>;
|
||||
}
|
||||
else {
|
||||
return <span>{val}<br></br></span>;
|
||||
return <span style={{ color: semanticColors.bodyText }}>{val}<br></br></span>;
|
||||
}
|
||||
})}
|
||||
</span>;
|
||||
|
@ -703,6 +736,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
maxDisplayablePersonas={3}
|
||||
overflowButtonType={OverflowButtonType.descriptive}
|
||||
overflowButtonProps={overflowButtonProps}
|
||||
|
||||
/>;
|
||||
}
|
||||
else {
|
||||
|
@ -716,10 +750,10 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
if (value) {
|
||||
result = <span>{value.map((val, index) => {
|
||||
if (index + 1 == value.length) {
|
||||
return <span>{val}</span>;
|
||||
return <span style={{ color: semanticColors.bodyText }}>{val}</span>;
|
||||
}
|
||||
else {
|
||||
return <span>{val}<br></br></span>;
|
||||
return <span style={{ color: semanticColors.bodyText }}>{val}<br></br></span>;
|
||||
}
|
||||
})}
|
||||
</span>;
|
||||
|
@ -731,7 +765,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
}
|
||||
case SharePointType.Lookup:
|
||||
if (value) {
|
||||
result = <Link href="#">{value}</Link>;
|
||||
result = <Link style={{ color: semanticColors.bodyText }} href="#">{value}</Link>;
|
||||
}
|
||||
else {
|
||||
result = <span></span>;
|
||||
|
@ -741,10 +775,10 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
if (value) {
|
||||
result = <span>{value.map((val, index) => {
|
||||
if (index + 1 == value.length) {
|
||||
return <span>{val}</span>;
|
||||
return <span style={{ color: semanticColors.bodyText }}>{val}</span>;
|
||||
}
|
||||
else {
|
||||
return <span>{val}<br></br></span>;
|
||||
return <span style={{ color: semanticColors.bodyText }}>{val}<br></br></span>;
|
||||
}
|
||||
})}
|
||||
</span>;
|
||||
|
@ -757,10 +791,10 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
if (value) {
|
||||
result = <span>{value.map((val, index) => {
|
||||
if (index + 1 == value.length) {
|
||||
return <Link href="#">{val}</Link>;
|
||||
return <Link style={{ color: semanticColors.bodyText }} href="#">{val}</Link>;
|
||||
}
|
||||
else {
|
||||
return <span><Link href="#">{val}</Link><br></br></span>;
|
||||
return <span><Link style={{ color: semanticColors.bodyText }} href="#">{val}</Link><br></br></span>;
|
||||
}
|
||||
})}
|
||||
</span>;
|
||||
|
@ -771,7 +805,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
break;
|
||||
case SharePointType.Url:
|
||||
if (value && value.Url) {
|
||||
result = <Link href={value.Url}>{value.Description}</Link>;
|
||||
result = <Link href={value.Url} style={{ color: semanticColors.bodyText }}>{value.Description}</Link>;
|
||||
}
|
||||
else {
|
||||
result = <span></span>;
|
||||
|
@ -809,7 +843,7 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
break;
|
||||
}
|
||||
default:
|
||||
result = <span>{value}</span>;
|
||||
result = <span style={{ color: semanticColors.bodyText }}>{value}</span>;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1025,75 +1059,82 @@ export default class IListdSearchWebPart extends React.Component<IListSearchProp
|
|||
let clearAllButton = this.props.ClearAllFiltersBtnColor == "white" ? <DefaultButton text={this.props.ClearAllFiltersBtnText} className={styles.btn} onClick={(ev) => this._clearAllFilters()} /> :
|
||||
<PrimaryButton text={this.props.ClearAllFiltersBtnText} className={styles.btn} onClick={(ev) => this._clearAllFilters()} />;
|
||||
return (
|
||||
<div className={styles.listSearch} style={{ backgroundColor: semanticColors.bodyBackground }}>
|
||||
<div className={styles.listSearch} style={{ backgroundColor: semanticColors.bodyBackground, color: semanticColors.bodyText }}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column}>
|
||||
{this.state.isLoading ?
|
||||
<Spinner label={strings.ListSearchLoading} size={SpinnerSize.large} style={{ backgroundColor: semanticColors.bodyBackground }} /> :
|
||||
this.state.errorMsg ?
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.error}
|
||||
isMultiline={false}
|
||||
truncated={true}>
|
||||
<b>{this.state.errorHeader}</b>{this.state.errorMsg}
|
||||
</MessageBar> :
|
||||
<React.Fragment>
|
||||
{this.props.clickEnabled && !this.state.isModalHidden && this.state.selectedItem && this.GetOnClickAction()}
|
||||
<div className={styles.rowTopInformation}>
|
||||
{this.props.GeneralFilter &&
|
||||
<div className={this.props.ShowClearAllFilters ? styles.ColGeneralFilterWithBtn : styles.ColGeneralFilterOnly}>
|
||||
<WebPartTitle title={this.props.title} updateProperty={(value: string) => this.props.updateTitle(value)} displayMode={this.props.displayMode} placeholder={strings.WebPartTitlePlaceHolder}></WebPartTitle>
|
||||
{this.state.errorMsg ?
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.error}
|
||||
isMultiline={false}
|
||||
truncated={true}>
|
||||
<b>{this.state.errorHeader}</b>{this.state.errorMsg}
|
||||
</MessageBar> :
|
||||
<React.Fragment>
|
||||
{this.props.clickEnabled && !this.state.isModalHidden && this.state.selectedItem && this.GetOnClickAction()}
|
||||
<div className={styles.rowTopInformation}>
|
||||
{this.props.GeneralFilter &&
|
||||
<div className={this.props.ShowClearAllFilters ? styles.ColGeneralFilterWithBtn : styles.ColGeneralFilterOnly}>
|
||||
<Shimmer isDataLoaded={!this.state.isLoading} height={37}>
|
||||
<SearchBox value={this.state.generalFilter} placeholder={this.props.GeneralFilterPlaceHolderText} onClear={() => this.clearGeneralFilter()} onChange={(ev, newValue) => this.filterListItemsByGeneralFilter(newValue, false, true, this.state.items, this.state.filterItems)} />
|
||||
</div>}
|
||||
<div className={styles.ColClearAll}>
|
||||
{this.props.ShowClearAllFilters && clearAllButton}
|
||||
</Shimmer>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.ColClearAll}>
|
||||
<Shimmer isDataLoaded={!this.state.isLoading}>
|
||||
{this.props.ShowClearAllFilters && clearAllButton}
|
||||
</Shimmer>
|
||||
</div>
|
||||
<div className={styles.rowData}>
|
||||
<div className={styles.colData}>
|
||||
{this.props.ShowItemCount && <div className={styles.template_resultCount}>{this.props.ItemCountText.replace("{itemCount}", `${this.state.filterItems.length}`)}</div>}
|
||||
<DetailsList
|
||||
items={this._getItems()}
|
||||
columns={this.state.columns}
|
||||
groups={this.props.groupByField && this.groups}
|
||||
groupProps={{
|
||||
showEmptyGroups: true,
|
||||
isAllGroupsCollapsed: true,
|
||||
}}
|
||||
onRenderDetailsFooter={this._checkIndividualFilter("footer") ? (detailsFooterProps) => this._onRenderDetails(detailsFooterProps) : undefined}
|
||||
onRenderDetailsHeader={this._checkIndividualFilter("header") ? (detailsHeaderProps) => this._onRenderDetails(detailsHeaderProps) : undefined}
|
||||
selectionMode={SelectionMode.none}
|
||||
onItemInvoked={this.props.clickEnabled && !this.props.oneClickOption ? this._onItemInvoked : null}
|
||||
onRenderRow={(props, defaultRender) => this.getOnRowClickRender(props, defaultRender)}
|
||||
onRenderItemColumn={this._renderItemColumn}
|
||||
/>
|
||||
{this.props.ShowPagination &&
|
||||
<div className={styles.paginationContainer}>
|
||||
<div className={styles.paginationContainer__paginationContainer}>
|
||||
<div className={`${styles.paginationContainer__paginationContainer__pagination}`}>
|
||||
<div className={styles.standard}>
|
||||
<Pagination
|
||||
activePage={this.state.activePage}
|
||||
firstPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='DoubleChevronLeft' />}
|
||||
lastPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='DoubleChevronRight' />}
|
||||
prevPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='ChevronLeft' />}
|
||||
nextPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='ChevronRight' />}
|
||||
activeLinkClass={styles.active}
|
||||
itemsCountPerPage={this.props.ItemsInPage}
|
||||
totalItemsCount={this.state.filterItems ? this.state.filterItems.length : 0}
|
||||
pageRangeDisplayed={5}
|
||||
onChange={this.handlePageChange.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rowData}>
|
||||
<div className={styles.colData}>
|
||||
{this.props.ShowItemCount &&
|
||||
<Shimmer isDataLoaded={!this.state.isLoading} width={"25%"}><div className={styles.template_resultCount}>{this.props.ItemCountText.replace("{itemCount}", `${this.state.filterItems ? this.state.filterItems.length : 0}`)}</div></Shimmer>
|
||||
}
|
||||
<ShimmeredDetailsList
|
||||
enableShimmer={this.state.isLoading}
|
||||
items={this._getItems()}
|
||||
columns={this.state.columns}
|
||||
groups={!this.state.isLoading && this.props.groupByField && this.groups}
|
||||
groupProps={{
|
||||
showEmptyGroups: true,
|
||||
isAllGroupsCollapsed: true,
|
||||
}}
|
||||
onRenderDetailsFooter={(detailsFooterProps) => this._onRenderDetails(detailsFooterProps, this._checkIndividualFilter("footer"), false)}
|
||||
onRenderDetailsHeader={(detailsHeaderProps) => this._onRenderDetails(detailsHeaderProps, this._checkIndividualFilter("header"), true)}
|
||||
selectionMode={SelectionMode.none}
|
||||
onItemInvoked={this.props.clickEnabled && !this.props.oneClickOption ? this._onItemInvoked : null}
|
||||
onRenderRow={(props, defaultRender) => this.getOnRowClickRender(props, defaultRender)}
|
||||
onRenderItemColumn={this._renderItemColumn}
|
||||
shimmerLines={this.props.ShowPagination ? this.props.ItemsInPage : 10}
|
||||
/>
|
||||
{this.props.ShowPagination &&
|
||||
<div className={styles.paginationContainer}>
|
||||
<div className={styles.paginationContainer__paginationContainer}>
|
||||
<div className={`${styles.paginationContainer__paginationContainer__pagination}`}>
|
||||
<div className={styles.standard}>
|
||||
<Pagination
|
||||
activePage={this.state.activePage}
|
||||
firstPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='DoubleChevronLeft' />}
|
||||
lastPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='DoubleChevronRight' />}
|
||||
prevPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='ChevronLeft' />}
|
||||
nextPageText={<Icon theme={this.props.themeVariant as ITheme} iconName='ChevronRight' />}
|
||||
activeLinkClass={styles.active}
|
||||
itemsCountPerPage={this.props.ItemsInPage}
|
||||
totalItemsCount={this.state.filterItems ? this.state.filterItems.length : 0}
|
||||
pageRangeDisplayed={5}
|
||||
onChange={this.handlePageChange.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>}
|
||||
</div>
|
||||
</React.Fragment>}
|
||||
</div>
|
||||
</div>
|
||||
</div >);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -105,7 +105,9 @@ define([], function () {
|
|||
InitialSearchText: "Initial search query",
|
||||
GeneralFilterInitialQueryEnabled: "Initial search enabled",
|
||||
GeneralFilterInitialQueryOption: "Initial search type",
|
||||
GeneralFilterInitialQueryTextValue: "Initial search value"
|
||||
|
||||
GeneralFilterInitialQueryTextValue: "Initial search value",
|
||||
WebPartTitlePlaceHolder: "Web part title",
|
||||
ModerAudienceEnabledTitle: "Modern audience enabled",
|
||||
ModernAudienceEnabledTitle: "Modern audience enabled",
|
||||
}
|
||||
});
|
||||
|
|
|
@ -105,6 +105,9 @@ declare interface IListSearchWebPartStrings {
|
|||
GeneralFilterInitialQueryEnabled: string;
|
||||
GeneralFilterInitialQueryOption: string;
|
||||
GeneralFilterInitialQueryTextValue: string;
|
||||
WebPartTitlePlaceHolder: string;
|
||||
ModerAudienceEnabledTitle: string;
|
||||
ModernAudienceEnabledTitle: string;
|
||||
}
|
||||
|
||||
declare module 'ListSearchWebPartStrings' {
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface IListData {
|
|||
Query: string;
|
||||
uniqueId: string;
|
||||
sortIdx: number;
|
||||
AudienceEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface IBaseFieldData {
|
||||
|
|
|
@ -2,17 +2,18 @@ import { SiteList } from "./IListConfigProps";
|
|||
import { SharePointType } from "./ISharePointFieldTypes";
|
||||
|
||||
export interface IMapQuery {
|
||||
[site: string]: Array<IMapQueryList>;
|
||||
[site: string]: Array<IMapQueryList>;
|
||||
}
|
||||
|
||||
export interface IMapQueryList {
|
||||
[list: string]: Array<IListSearchListQuery>;
|
||||
[list: string]: Array<IListSearchListQuery>;
|
||||
}
|
||||
|
||||
export interface IListSearchListQuery {
|
||||
list: SiteList;
|
||||
camlQuery?: string;
|
||||
viewName?: string;
|
||||
fields: Array<{ originalField: string, newField: string, fieldType: SharePointType }>;
|
||||
}
|
||||
list: SiteList;
|
||||
audienceEnabled: boolean;
|
||||
camlQuery?: string;
|
||||
viewName?: string;
|
||||
fields: Array<{ originalField: string, newField: string, fieldType: SharePointType }>;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,4 +9,9 @@ export default interface IResult {
|
|||
UniqueId: string;
|
||||
ServerUrl: string;
|
||||
FileLeafRef: string;
|
||||
}
|
||||
OData__ModernAudienceTargetUserField:Audience[];
|
||||
}
|
||||
|
||||
interface Audience{
|
||||
Name: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { graph } from "@pnp/graph";
|
||||
import "@pnp/graph/users";
|
||||
import "@pnp/graph/groups";
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import { PnPClientStorage } from "@pnp/common";
|
||||
import IGraphService from "./IGraphService";
|
||||
|
||||
|
||||
|
||||
export default class GraphService implements IGraphService {
|
||||
|
||||
private _storage: PnPClientStorage;
|
||||
private _localStorageKey: string = 'userGroups';
|
||||
// 1 day
|
||||
private _expiredTimeMillisecons: number = 8.64e+7;
|
||||
|
||||
public constructor(spfxContext: WebPartContext) {
|
||||
graph.setup({
|
||||
spfxContext
|
||||
});
|
||||
this._storage = new PnPClientStorage();
|
||||
}
|
||||
|
||||
public async getTransitiveMemberOf(): Promise<string[]> {
|
||||
try {
|
||||
this._storage.local.deleteExpired();
|
||||
let userGroups = this._storage.local.get(this._localStorageKey);
|
||||
if (!userGroups) {
|
||||
userGroups = await graph.me.getMemberGroups();
|
||||
this._storage.local.put(this._localStorageKey, userGroups, new Date(new Date().getTime() + this._expiredTimeMillisecons));
|
||||
}
|
||||
return userGroups;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export default interface IGraphService {
|
||||
getTransitiveMemberOf(): Promise<Array<string>>;
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { ListField } from "../model/IListConfigProps";
|
||||
import { IListSearchListQuery } from "../model/IMapQuery";
|
||||
import GraphService from "./GraphService";
|
||||
|
||||
export default interface IListService {
|
||||
getListItems(listQueryOptions: IListSearchListQuery, listPropertyName: string, sitePropertyName: string, sitePropertyValue: string, rowLimit: number): Promise<Array<any>>;
|
||||
getListItems(listQueryOptions: IListSearchListQuery, listPropertyName: string, sitePropertyName: string, sitePropertyValue: string, rowLimit: number, graphService?: GraphService): Promise<Array<any>>;
|
||||
getListItemById(listQueryOptions: IListSearchListQuery, itemId: number): Promise<any>;
|
||||
getSiteListsTitle(): Promise<Array<any>>;
|
||||
getListFields(listTitle: string): Promise<Array<ListField>>;
|
||||
|
|
|
@ -11,9 +11,10 @@ import XMLParser from 'react-xml-parser';
|
|||
import { IWeb, Web } from '@pnp/sp/webs';
|
||||
import { SharePointType } from '../model/ISharePointFieldTypes';
|
||||
import IResult from '../model/IResult';
|
||||
import { isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
import { intersection, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
import { ListField, SiteList } from '../model/IListConfigProps';
|
||||
import { IListSearchListQuery } from '../model/IMapQuery';
|
||||
import GraphService from './GraphService';
|
||||
|
||||
|
||||
export interface QueryHelperEntity {
|
||||
|
@ -21,9 +22,12 @@ export interface QueryHelperEntity {
|
|||
expandFields: string[];
|
||||
}
|
||||
|
||||
|
||||
export default class ListService implements IListService {
|
||||
private web: IWeb;
|
||||
private baseUrl: string;
|
||||
private static SharePointOnlineAudienceOOTBFieldName = "OData__ModernAudienceTargetUserField";
|
||||
public static MAX_TOP: number = 5000;
|
||||
|
||||
constructor(siteUrl: string, useCache: boolean, cacheTime?: number, cacheType?: "session" | "local") {
|
||||
sp.setup({
|
||||
|
@ -40,7 +44,102 @@ export default class ListService implements IListService {
|
|||
this.baseUrl = siteUrl;
|
||||
}
|
||||
|
||||
private GetViewFieldsWithId(listQueryOptions: IListSearchListQuery, isCamlQuery: boolean): QueryHelperEntity {
|
||||
public async getListItems(listQueryOptions: IListSearchListQuery, listPropertyName: string, sitePropertyName: string, sitePropertyValue: string, rowLimit: number, graphService?: GraphService): Promise<Array<IResult>> {
|
||||
try {
|
||||
let camlQuery: boolean = false;
|
||||
let items: any = undefined;
|
||||
let queryConfig: QueryHelperEntity = this.GetViewFieldsWithId(listQueryOptions, !isEmpty(listQueryOptions.camlQuery) || !isEmpty(listQueryOptions.viewName), false);
|
||||
if (listQueryOptions.camlQuery) {
|
||||
let query = this.getCamlQueryWithViewFieldsAndRowLimit(listQueryOptions.camlQuery, queryConfig, rowLimit);
|
||||
items = await this.getListItemsByCamlQuery(listQueryOptions.list.Id, query, queryConfig);
|
||||
}
|
||||
else {
|
||||
if (listQueryOptions.viewName) {
|
||||
let viewInfo: any = await this.web.lists.getById(listQueryOptions.list.Id).views.getByTitle(listQueryOptions.viewName).select("ViewQuery").get();
|
||||
let query = this.getCamlQueryWithViewFieldsAndRowLimit(`<View><Query>${viewInfo.ViewQuery}</Query></View>`, queryConfig, rowLimit);
|
||||
items = await this.getListItemsByCamlQuery(listQueryOptions.list.Id, query, queryConfig);
|
||||
}
|
||||
else {
|
||||
items = await sp.web.lists.getById(listQueryOptions.list.Id).items
|
||||
.select(queryConfig.viewFields.join(','))
|
||||
.top(rowLimit || ListService.MAX_TOP)
|
||||
.expand(queryConfig.expandFields.join(',')).usingCaching().get();
|
||||
}
|
||||
}
|
||||
|
||||
if (listQueryOptions.audienceEnabled && graphService) {
|
||||
let userGroups: string[] = await graphService.getTransitiveMemberOf();
|
||||
items = this.getAudienceItems(items, userGroups);
|
||||
}
|
||||
|
||||
let mappedItems = items.map((i: IResult) => {
|
||||
i.FileExtension = this.GetFileExtension(i.FileLeafRef);
|
||||
i.SiteUrl = this.baseUrl;
|
||||
i.ListName = listQueryOptions.list.Title;
|
||||
i.List = listQueryOptions.list;
|
||||
|
||||
listQueryOptions.fields.map(field => {
|
||||
i = this.GetItemValue(i, field, camlQuery);
|
||||
});
|
||||
|
||||
if (listPropertyName) {
|
||||
i[listPropertyName] = listQueryOptions.list.Title;
|
||||
}
|
||||
if (sitePropertyName) {
|
||||
i[sitePropertyName] = sitePropertyValue;
|
||||
}
|
||||
return i;
|
||||
});
|
||||
return mappedItems;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getListItemById(listQueryOptions: IListSearchListQuery, itemId: number): Promise<any> {
|
||||
try {
|
||||
let queryConfig: QueryHelperEntity = this.GetViewFieldsWithId(listQueryOptions, false, true);
|
||||
return this.web.lists.getById(listQueryOptions.list.Id).items.getById(itemId).select(queryConfig.viewFields.join(',')).expand(queryConfig.expandFields.join(',')).usingCaching().get();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getSiteListsTitle(): Promise<Array<SiteList>> {
|
||||
try {
|
||||
return this.web.lists.filter('Hidden eq false').select('Title,Id').get();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getListFields(listId: string): Promise<Array<ListField>> {
|
||||
try {
|
||||
return this.web.lists.getById(listId).fields.select('EntityPropertyName,Title,InternalName,TypeAsString').get();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
private getAudienceItems(itemsToFilter: IResult[], userGroups: string[]) {
|
||||
let results: IResult[] = [];
|
||||
userGroups && itemsToFilter.map(item => {
|
||||
let itemAudiencesIds: string[] = item.OData__ModernAudienceTargetUserField && item.OData__ModernAudienceTargetUserField.map(audience => { return audience.Name.split("|")[2]; });
|
||||
if (itemAudiencesIds) {
|
||||
let matches: string[] = intersection(itemAudiencesIds, userGroups);
|
||||
if (matches && matches.length > 0) {
|
||||
results.push(item);
|
||||
}
|
||||
}
|
||||
else {
|
||||
results.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private GetViewFieldsWithId(listQueryOptions: IListSearchListQuery, isCamlQuery: boolean, isItemId: boolean): QueryHelperEntity {
|
||||
let result: QueryHelperEntity = { expandFields: [], viewFields: ['ServerUrl', 'FileLeafRef', 'Id', 'UniqueId'] };
|
||||
let hasToAddFieldsAsText: boolean = false;
|
||||
listQueryOptions.fields.map(field => {
|
||||
|
@ -100,6 +199,11 @@ export default class ListService implements IListService {
|
|||
result.expandFields.push('FieldValuesAsText');
|
||||
}
|
||||
|
||||
if (listQueryOptions.audienceEnabled && !isItemId) {
|
||||
result.expandFields.push(ListService.SharePointOnlineAudienceOOTBFieldName);
|
||||
result.viewFields.push(`${ListService.SharePointOnlineAudienceOOTBFieldName}/Name`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -186,90 +290,6 @@ export default class ListService implements IListService {
|
|||
return item;
|
||||
}
|
||||
|
||||
public async getListItems(listQueryOptions: IListSearchListQuery, listPropertyName: string, sitePropertyName: string, sitePropertyValue: string, rowLimit: number): Promise<Array<IResult>> {
|
||||
try {
|
||||
let camlQuery: boolean = false;
|
||||
let items: any = undefined;
|
||||
let queryConfig: QueryHelperEntity = this.GetViewFieldsWithId(listQueryOptions, !isEmpty(listQueryOptions.camlQuery) || !isEmpty(listQueryOptions.viewName));
|
||||
if (listQueryOptions.camlQuery) {
|
||||
let query = this.getCamlQueryWithViewFieldsAndRowLimit(listQueryOptions.camlQuery, queryConfig, rowLimit);
|
||||
items = await this.getListItemsByCamlQuery(listQueryOptions.list.Id, query, queryConfig);
|
||||
}
|
||||
else {
|
||||
if (listQueryOptions.viewName) {
|
||||
let viewInfo: any = await this.web.lists.getById(listQueryOptions.list.Id).views.getByTitle(listQueryOptions.viewName).select("ViewQuery").get();
|
||||
let query = this.getCamlQueryWithViewFieldsAndRowLimit(`<View><Query>${viewInfo.ViewQuery}</Query></View>`, queryConfig, rowLimit);
|
||||
items = await this.getListItemsByCamlQuery(listQueryOptions.list.Id, query, queryConfig);
|
||||
}
|
||||
else {
|
||||
if (rowLimit) {
|
||||
if (queryConfig.expandFields && queryConfig.expandFields.length > 0) {
|
||||
items = await this.web.lists.getById(listQueryOptions.list.Id).items.select(queryConfig.viewFields.join(',')).expand(queryConfig.expandFields.join(',')).usingCaching().get();
|
||||
}
|
||||
else {
|
||||
items = await this.web.lists.getById(listQueryOptions.list.Id).items.top(rowLimit).select(queryConfig.viewFields.join(',')).usingCaching().get();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (queryConfig.expandFields && queryConfig.expandFields.length > 0) {
|
||||
items = await this.web.lists.getById(listQueryOptions.list.Id).items.select(queryConfig.viewFields.join(',')).expand(queryConfig.expandFields.join(',')).usingCaching().get();
|
||||
}
|
||||
else {
|
||||
items = await this.web.lists.getById(listQueryOptions.list.Id).items.select(queryConfig.viewFields.join(',')).usingCaching().get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
let mappedItems = items.map((i: IResult) => {
|
||||
i.FileExtension = this.GetFileExtension(i.FileLeafRef);
|
||||
i.SiteUrl = this.baseUrl;
|
||||
i.ListName = listQueryOptions.list.Title;
|
||||
i.List = listQueryOptions.list;
|
||||
|
||||
listQueryOptions.fields.map(field => {
|
||||
i = this.GetItemValue(i, field, camlQuery);
|
||||
});
|
||||
|
||||
if (listPropertyName) {
|
||||
i[listPropertyName] = listQueryOptions.list.Title;
|
||||
}
|
||||
if (sitePropertyName) {
|
||||
i[sitePropertyName] = sitePropertyValue;
|
||||
}
|
||||
return i;
|
||||
});
|
||||
return mappedItems;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getListItemById(listQueryOptions: IListSearchListQuery, itemId: number): Promise<any> {
|
||||
try {
|
||||
let queryConfig: QueryHelperEntity = this.GetViewFieldsWithId(listQueryOptions, false);
|
||||
return this.web.lists.getById(listQueryOptions.list.Id).items.getById(itemId).select(queryConfig.viewFields.join(',')).expand(queryConfig.expandFields.join(',')).usingCaching().get();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getSiteListsTitle(): Promise<Array<SiteList>> {
|
||||
try {
|
||||
return this.web.lists.filter('Hidden eq false').select('Title,Id').get();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getListFields(listId: string): Promise<Array<ListField>> {
|
||||
try {
|
||||
return this.web.lists.getById(listId).fields.select('EntityPropertyName,Title,InternalName,TypeAsString').get();
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async getListItemsByCamlQuery(listId: string, camlQuery: string, queryConfig: QueryHelperEntity): Promise<Array<any>> {
|
||||
try {
|
||||
const caml: ICamlQuery = {
|
||||
|
|
Loading…
Reference in New Issue