Features added and fix for issue #1504
This commit is contained in:
parent
fc3919f9b3
commit
8566f9fe72
|
@ -28,6 +28,8 @@
|
|||
|
||||
![directory](/samples/react-directory/assets/react-directory5.jpg)
|
||||
|
||||
![directory](./assets/react-directory-withPaging.png)
|
||||
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
@ -46,6 +48,8 @@ Property |Type|Required| comments
|
|||
--------------------|----|--------|----------
|
||||
Title | Text| no|WebPart Title
|
||||
searchFirstName | boolean|no| Lastname or Firstname search query
|
||||
Properties to search | text | no | By default **FirstName,LastName,WorkEmail,Department** are used for search. You can add custom properties separated by comma.
|
||||
Results per page | number | Number of people result to be displayed per page. Max of **20** is allowed, default of **10** is set.
|
||||
|
||||
|
||||
|
||||
|
@ -57,6 +61,7 @@ Solution|Author(s)
|
|||
--------|---------
|
||||
Directory Web Part|João Mendes
|
||||
Directory Web Part| Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
|
||||
Directory Web Part| Sudharsan K ([@sudharsank](https://twitter.com/sudharsank))
|
||||
|
||||
## Version history
|
||||
|
||||
|
@ -64,6 +69,7 @@ Version|Date|Comments
|
|||
-------|----|--------
|
||||
1.0.0|July 29, 2019|Initial release
|
||||
1.0.1|July 19, 2020|Bugfix and mock-service for workbench (```LivePersonaCard``` not supported in workbench)
|
||||
2.0.0.0|Sep 18 2020|React hooks, paging, dynamic search props, result alignment using office ui fabric stack.
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"name": "Search Directory",
|
||||
"id": "5b62bc16-3a71-461d-be2f-16bfcb011e8a",
|
||||
"version": "1.0.1.0",
|
||||
"version": "2.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -26,9 +26,12 @@
|
|||
"@types/jquery": "^3.3.30",
|
||||
"@uifabric/fluent-theme": "^0.16.9",
|
||||
"jquery": "^3.4.1",
|
||||
"lodash": "^4.17.20",
|
||||
"office-ui-fabric-react": "6.214.0",
|
||||
"react": "16.7.0",
|
||||
"react-dom": "16.7.0"
|
||||
"react-dom": "16.7.0",
|
||||
"react-js-pagination": "^3.0.3",
|
||||
"throttle-debounce": "^2.3.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
|
|
|
@ -3,5 +3,6 @@ import { PeoplePickerEntity } from '@pnp/sp';
|
|||
export interface ISPServices {
|
||||
|
||||
searchUsers(searchString: string, searchFirstName: boolean);
|
||||
searchUsersNew(searchString: string, srchQry: string, isInitialSearch: boolean, pageNumber?: number);
|
||||
|
||||
}
|
|
@ -109,4 +109,35 @@ export class spMockServices implements ISPServices {
|
|||
|
||||
}
|
||||
|
||||
public async searchUsersNew(searchString: string, srchQry: string, isInitialSearch: boolean, pageNumber?: number) {
|
||||
let filtervalue = searchString.trim().toLowerCase();
|
||||
if (searchString.length > 0 && filtervalue.lastIndexOf("*") == searchString.length - 1) {
|
||||
// remove last '*'
|
||||
filtervalue = filtervalue.substring(0, searchString.length - 1);
|
||||
}
|
||||
if (!filtervalue || filtervalue.length === 0) {
|
||||
throw new Error("No valid Input.");
|
||||
}
|
||||
|
||||
|
||||
const searchresult = !!isInitialSearch ?
|
||||
this.sampleData.filter(p => p.FirstName.toLowerCase().indexOf(filtervalue) === 0) :
|
||||
this.sampleData.filter(p => p.LastName.toLowerCase().indexOf(filtervalue) === 0);
|
||||
|
||||
const timeout = Math.floor(Math.random() * (1000)) + 1;
|
||||
const resultdata = {
|
||||
ElapsedTime: timeout,
|
||||
RowCount: searchresult.length,
|
||||
TotalRows: searchresult.length,
|
||||
PrimarySearchResults: searchresult
|
||||
};
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(resultdata);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -7,38 +7,86 @@ import { ISPServices } from "./ISPServices";
|
|||
|
||||
|
||||
export class spservices implements ISPServices {
|
||||
|
||||
|
||||
|
||||
constructor(private context: WebPartContext) {
|
||||
|
||||
sp.setup({
|
||||
spfxContext: this.context
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async searchUsers(searchString: string, searchFirstName: boolean): Promise<SearchResults> {
|
||||
const _search = !searchFirstName ? `LastName:${searchString}*` : `FirstName:${searchString}*`;
|
||||
const searchProperties: string[] = ["FirstName", "LastName", "PreferredName", "WorkEmail", "OfficeNumber", "PictureURL", "WorkPhone", "MobilePhone", "JobTitle", "Department", "Skills", "PastProjects", "BaseOfficeLocation", "SPS-UserType", "GroupId"];
|
||||
try {
|
||||
if (!searchString) return undefined;
|
||||
let users = await sp.searchWithCaching(<SearchQuery>{
|
||||
Querytext: _search,
|
||||
RowLimit: 500,
|
||||
EnableInterleaving: true,
|
||||
SelectProperties: searchProperties,
|
||||
SourceId: 'b09a7990-05ea-4af9-81ef-edfab16c4e31',
|
||||
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
return users;
|
||||
} catch (error) {
|
||||
Promise.reject(error);
|
||||
constructor(private context: WebPartContext) {
|
||||
sp.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
}
|
||||
|
||||
public async searchUsers(searchString: string, searchFirstName: boolean): Promise<SearchResults> {
|
||||
const _search = !searchFirstName ? `LastName:${searchString}*` : `FirstName:${searchString}*`;
|
||||
const searchProperties: string[] = ["FirstName", "LastName", "PreferredName", "WorkEmail", "OfficeNumber", "PictureURL", "WorkPhone", "MobilePhone", "JobTitle", "Department", "Skills", "PastProjects", "BaseOfficeLocation", "SPS-UserType", "GroupId"];
|
||||
try {
|
||||
if (!searchString) return undefined;
|
||||
let users = await sp.searchWithCaching(<SearchQuery>{
|
||||
Querytext: _search,
|
||||
RowLimit: 500,
|
||||
EnableInterleaving: true,
|
||||
SelectProperties: searchProperties,
|
||||
SourceId: 'b09a7990-05ea-4af9-81ef-edfab16c4e31',
|
||||
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
|
||||
});
|
||||
return users;
|
||||
} catch (error) {
|
||||
Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async _getImageBase64 (pictureUrl: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image();
|
||||
image.addEventListener("load", () => {
|
||||
let tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = image.width,
|
||||
tempCanvas.height = image.height,
|
||||
tempCanvas.getContext("2d").drawImage(image, 0, 0);
|
||||
let base64Str;
|
||||
try {
|
||||
base64Str = tempCanvas.toDataURL("image/png");
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
resolve(base64Str);
|
||||
});
|
||||
image.src = pictureUrl;
|
||||
});
|
||||
}
|
||||
|
||||
public async searchUsersNew(searchString: string, srchQry: string, isInitialSearch: boolean, pageNumber?: number): Promise<SearchResults> {
|
||||
//const _search = !isInitialSearch ? srchQry : `FirstName:${searchString}*`;
|
||||
let qrytext: string = '';
|
||||
if (isInitialSearch) qrytext = `FirstName:${searchString}* OR LastName:${searchString}*`;
|
||||
else {
|
||||
if (srchQry) qrytext = srchQry;
|
||||
else {
|
||||
if (searchString) qrytext = searchString;
|
||||
}
|
||||
if (qrytext.length <= 0) qrytext = `*`;
|
||||
}
|
||||
console.log(qrytext);
|
||||
const searchProperties: string[] = ["FirstName", "LastName", "PreferredName", "WorkEmail", "OfficeNumber", "PictureURL", "WorkPhone", "MobilePhone", "JobTitle", "Department", "Skills", "PastProjects", "BaseOfficeLocation", "SPS-UserType", "GroupId"];
|
||||
try {
|
||||
let users = await sp.search(<SearchQuery>{
|
||||
Querytext: qrytext,
|
||||
RowLimit: 500,
|
||||
EnableInterleaving: true,
|
||||
SelectProperties: searchProperties,
|
||||
SourceId: 'b09a7990-05ea-4af9-81ef-edfab16c4e31',
|
||||
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
|
||||
});
|
||||
console.log(users);
|
||||
if (users && users.PrimarySearchResults.length > 0) {
|
||||
for (let index = 0; index < users.PrimarySearchResults.length; index++) {
|
||||
let user: any = users.PrimarySearchResults[index];
|
||||
if (user.PictureURL) {
|
||||
user = { ...user, PictureURL: `/_layouts/15/userphoto.aspx?size=M&accountname=${user.WorkEmail}` };
|
||||
users.PrimarySearchResults[index] = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
return users;
|
||||
} catch (error) {
|
||||
Promise.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
"officeFabricIconFontName": "ProfileSearch",
|
||||
"properties": {
|
||||
"title": "Directory",
|
||||
"searchFirstName": 0
|
||||
"searchFirstName": 0,
|
||||
"searchProps": "FirstName,LastName,WorkEmail,Department",
|
||||
"pageSize": 10
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -2,74 +2,108 @@ import * as React from "react";
|
|||
import * as ReactDom from "react-dom";
|
||||
import { Version } from "@microsoft/sp-core-library";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneToggle,
|
||||
IPropertyPaneToggleProps
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneToggle,
|
||||
IPropertyPaneToggleProps,
|
||||
PropertyPaneSlider
|
||||
} from "@microsoft/sp-property-pane";
|
||||
|
||||
import * as strings from "DirectoryWebPartStrings";
|
||||
import Directory from "./components/Directory";
|
||||
import DirectoryHook from "./components/DirectoryHook";
|
||||
import { IDirectoryProps } from "./components/IDirectoryProps";
|
||||
|
||||
export interface IDirectoryWebPartProps {
|
||||
title: string;
|
||||
searchFirstName: boolean;
|
||||
title: string;
|
||||
searchFirstName: boolean;
|
||||
searchProps: string;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export default class DirectoryWebPart extends BaseClientSideWebPart<
|
||||
IDirectoryWebPartProps
|
||||
> {
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IDirectoryProps> = React.createElement(
|
||||
Directory,
|
||||
{
|
||||
title: this.properties.title,
|
||||
context: this.context,
|
||||
searchFirstName: this.properties.searchFirstName,
|
||||
displayMode: this.displayMode,
|
||||
updateProperty: (value: string) => {
|
||||
this.properties.title = value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse("1.0");
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
IDirectoryWebPartProps
|
||||
> {
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IDirectoryProps> = React.createElement(
|
||||
// Directory,
|
||||
// {
|
||||
// title: this.properties.title,
|
||||
// context: this.context,
|
||||
// searchFirstName: this.properties.searchFirstName,
|
||||
// displayMode: this.displayMode,
|
||||
// updateProperty: (value: string) => {
|
||||
// this.properties.title = value;
|
||||
// }
|
||||
// },
|
||||
DirectoryHook,
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("title", {
|
||||
label: strings.TitleFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle("searchFirstName", {
|
||||
checked: false,
|
||||
label: "Search on First Name ?"
|
||||
})
|
||||
]
|
||||
title: this.properties.title,
|
||||
context: this.context,
|
||||
searchFirstName: this.properties.searchFirstName,
|
||||
displayMode: this.displayMode,
|
||||
updateProperty: (value: string) => {
|
||||
this.properties.title = value;
|
||||
},
|
||||
searchProps: this.properties.searchProps,
|
||||
pageSize: this.properties.pageSize
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse("1.0");
|
||||
}
|
||||
|
||||
protected get disableReactivePropertyChanges(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("title", {
|
||||
label: strings.TitleFieldLabel
|
||||
}),
|
||||
PropertyPaneToggle("searchFirstName", {
|
||||
checked: false,
|
||||
label: "Search on First Name ?"
|
||||
}),
|
||||
PropertyPaneTextField('searchProps', {
|
||||
label: strings.SearchPropsLabel,
|
||||
description: strings.SearchPropsDesc,
|
||||
value: this.properties.searchProps,
|
||||
multiline: false,
|
||||
resizable: false
|
||||
}),
|
||||
PropertyPaneSlider('pageSize', {
|
||||
label: 'Results per page',
|
||||
showValue: true,
|
||||
max: 20,
|
||||
min: 2,
|
||||
step: 2,
|
||||
value: this.properties.pageSize
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
.directory {
|
||||
.dropDownSortBy {
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
|
@ -98,4 +97,18 @@
|
|||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.alphabets {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
}
|
||||
.searchTextBox {
|
||||
min-width: 180px;
|
||||
max-width: 300px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,4 +6,6 @@ export interface IDirectoryProps {
|
|||
context: WebPartContext;
|
||||
searchFirstName: boolean;
|
||||
updateProperty: (value: string) => void;
|
||||
searchProps?: string;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
display: inline-block;
|
||||
margin-top: 15px;
|
||||
margin-right: 15px;
|
||||
|
||||
min-width: 300;
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.documentCard {
|
||||
|
@ -25,3 +25,13 @@
|
|||
margin-top: 10;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.textOverflow {
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ export class PersonaCard extends React.Component<
|
|||
LIVE_PERSONA_COMPONENT_ID
|
||||
);
|
||||
const livePersonaCard: any = sharedLibrary.LivePersonaCard;
|
||||
console.log(livePersonaCard);
|
||||
this.setState({ livePersonaCard: livePersonaCard });
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +114,7 @@ export class PersonaCard extends React.Component<
|
|||
''
|
||||
)}
|
||||
{this.props.profileProperties.Location ? (
|
||||
<div>
|
||||
<div className={styles.textOverflow}>
|
||||
<Icon iconName="Poi" style={{ fontSize: '12px' }} />
|
||||
<span style={{ marginLeft: 5, fontSize: '12px' }}>
|
||||
{' '}
|
||||
|
|
|
@ -6,6 +6,10 @@ define([], function() {
|
|||
"PropertyPaneDescription": "Search People from Organization Directory",
|
||||
"BasicGroupName": "Properties",
|
||||
"TitleFieldLabel": "Web Part Title",
|
||||
"DirectoryMessage": "No users found in directory"
|
||||
"DirectoryMessage": "No users found in directory",
|
||||
"LoadingText": "Searching for user. Please wait...",
|
||||
"SearchPropsLabel": "Properties to search",
|
||||
"SearchPropsDesc": "Enter the properties separated by comma to be used for search",
|
||||
"PagingLabel": "Results per page"
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,7 +6,10 @@ declare interface IDirectoryWebPartStrings {
|
|||
BasicGroupName: string;
|
||||
TitleFieldLabel: string;
|
||||
DirectoryMessage: string;
|
||||
|
||||
LoadingText: string;
|
||||
SearchPropsLabel: string;
|
||||
SearchPropsDesc: string;
|
||||
PagingLabel: string;
|
||||
}
|
||||
|
||||
declare module 'DirectoryWebPartStrings' {
|
||||
|
|
Loading…
Reference in New Issue