Skip to content

Commit c433286

Browse files
Add folder picker control
1 parent 5fc3a09 commit c433286

File tree

7 files changed

+285
-28
lines changed

7 files changed

+285
-28
lines changed

src/FolderPicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/folderPicker/index';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// @import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
2+
@import '~office-ui-fabric-react/dist/sass/References.scss';
3+
4+
5+
.folderPicker {
6+
display: flex;
7+
align-items: center;
8+
9+
.selection {
10+
width: 90%;
11+
}
12+
13+
.selectFolderLabel {
14+
color: $ms-color-neutralSecondary;
15+
}
16+
17+
.selectFolder {
18+
align-items: center;
19+
display: flex;
20+
margin-right: 5px;
21+
max-width: 90%;
22+
span {
23+
white-space: nowrap;
24+
overflow: hidden;
25+
text-overflow: ellipsis;
26+
}
27+
}
28+
29+
& .selectButton {
30+
width: 10%;
31+
display: flex;
32+
justify-content: center;
33+
}
34+
}
35+
36+
.actions {
37+
button {
38+
margin-right: 15px;
39+
}
40+
}
41+
42+
label.required::after {
43+
content: " *";
44+
color: rgb(168, 0, 0);
45+
padding-right: 12px;
46+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import * as React from 'react';
2+
import styles from './FolderPicker.module.scss';
3+
import { IFolderPickerProps, IFolderPickerState } from '.';
4+
import { IFolder } from '../../services/IFolderExplorerService';
5+
import { IconButton, PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
6+
import { Label } from 'office-ui-fabric-react/lib/Label';
7+
import { Link } from 'office-ui-fabric-react/lib/Link';
8+
import { getId } from 'office-ui-fabric-react/lib/Utilities';
9+
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
10+
import { FolderExplorer } from '../folderExplorer/FolderExplorer';
11+
12+
13+
export default class FolderPicker extends React.Component<IFolderPickerProps, IFolderPickerState> {
14+
15+
private _folderLinkId = getId('folderLink');
16+
private _selectedFolder: IFolder;
17+
18+
constructor(props: IFolderPickerProps) {
19+
super(props);
20+
21+
this.state = {
22+
showPanel: false,
23+
selectedFolder: this.props.defaultFolder
24+
};
25+
}
26+
27+
public componentWillReceiveProps(nextProps: IFolderPickerProps) {
28+
29+
this.setState({
30+
selectedFolder: nextProps.defaultFolder,
31+
});
32+
33+
}
34+
35+
public render(): React.ReactElement<IFolderPickerProps> {
36+
return (
37+
<div>
38+
{this.props.label &&
39+
<Label className={this.props.required ? styles.required : ''} htmlFor={this._folderLinkId}>{this.props.label}</Label>
40+
}
41+
<div className={styles.folderPicker}>
42+
<div className={styles.selection}>
43+
{!this.state.selectedFolder &&
44+
<span className={styles.selectFolderLabel}>Select a folder</span>
45+
}
46+
{this.state.selectedFolder &&
47+
<div className={styles.selectFolder}>
48+
<Link className={styles.selectFolder} target='_blank' data-interception="off" id={this._folderLinkId} href={this.state.selectedFolder.ServerRelativeUrl}>
49+
<span title={this.state.selectedFolder.Name}>{this.state.selectedFolder.Name}</span>
50+
</Link>
51+
<IconButton
52+
iconProps={{ iconName: 'Cancel' }}
53+
title="Delete selection"
54+
ariaLabel="Delete selection"
55+
onClick={this._resetSelection}
56+
disabled={this.props.disabled}
57+
/>
58+
</div>
59+
}
60+
</div>
61+
<div className={styles.selectButton}>
62+
<IconButton
63+
iconProps={{ iconName: 'FolderList' }}
64+
title="Select folder"
65+
ariaLabel="Select folder"
66+
disabled={this.props.disabled}
67+
onClick={this._showPanel}
68+
/>
69+
</div>
70+
</div>
71+
72+
<Panel
73+
isOpen={this.state.showPanel}
74+
type={PanelType.medium}
75+
onDismiss={this._hidePanel}
76+
headerText="Select folder"
77+
closeButtonAriaLabel="Close"
78+
onRenderFooterContent={this._onRenderFooterContent}
79+
>
80+
<div>
81+
<FolderExplorer
82+
context={this.props.context}
83+
rootFolder={this.props.rootFolder}
84+
defaultFolder={this.state.selectedFolder}
85+
onSelect={this._onFolderSelect}
86+
canCreateFolders={this.props.canCreateFolders}
87+
/>
88+
</div>
89+
</Panel>
90+
91+
</div>
92+
);
93+
}
94+
95+
private _showPanel = () => {
96+
this.setState({ showPanel: true });
97+
}
98+
99+
private _hidePanel = () => {
100+
this.setState({ showPanel: false });
101+
}
102+
103+
private _onRenderFooterContent = () => {
104+
return (
105+
<div className={styles.actions}>
106+
<PrimaryButton iconProps={{ iconName: 'Save' }} onClick={this._onFolderSave}>
107+
Save
108+
</PrimaryButton>
109+
<DefaultButton iconProps={{ iconName: 'Cancel' }} onClick={this._hidePanel}>
110+
Cancel
111+
</DefaultButton>
112+
</div>
113+
);
114+
}
115+
116+
private _onFolderSelect = (folder: IFolder): void => {
117+
this._selectedFolder = folder;
118+
}
119+
120+
private _onFolderSave = (): void => {
121+
this.setState({
122+
selectedFolder: this._selectedFolder,
123+
showPanel: false,
124+
});
125+
126+
this.props.onSelect(this._selectedFolder);
127+
}
128+
129+
private _resetSelection = (): void => {
130+
this._selectedFolder = null;
131+
132+
this.setState({
133+
selectedFolder: this._selectedFolder,
134+
});
135+
136+
this.props.onSelect(this._selectedFolder);
137+
}
138+
139+
140+
141+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { WebPartContext } from '@microsoft/sp-webpart-base';
2+
import { ExtensionContext } from '@microsoft/sp-extension-base';
3+
import { IFolder } from '../../services/IFolderExplorerService';
4+
5+
export interface IFolderPickerProps {
6+
/**
7+
* Current context
8+
*/
9+
context: WebPartContext | ExtensionContext;
10+
11+
/**
12+
* The field label
13+
*/
14+
label: string;
15+
16+
/**
17+
* The lowest level folder that can be explored. This can be the root folder of a library.
18+
*/
19+
rootFolder: IFolder;
20+
21+
/**
22+
* The default folder to be explored
23+
*/
24+
defaultFolder?: IFolder;
25+
26+
/**
27+
* Is the field required
28+
*/
29+
required?: boolean;
30+
31+
/**
32+
* Is the field disabled
33+
*/
34+
disabled?: boolean;
35+
36+
/**
37+
* Allow current user to create folders on the target location. If enabled, you need to ensure that the user has the required permissions
38+
*/
39+
canCreateFolders?: boolean;
40+
41+
/**
42+
* Callback function called after a folder is selected
43+
* @argument folder The selected folder
44+
*/
45+
onSelect: (folder: IFolder) => void;
46+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IFolder } from '../../services/IFolderExplorerService';
2+
3+
export interface IFolderPickerState {
4+
showPanel: boolean;
5+
selectedFolder: IFolder;
6+
}

src/controls/folderPicker/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './FolderPicker';
2+
export * from './IFolderPickerProps';
3+
export * from './IFolderPickerState';

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ import {
5353
} from 'office-ui-fabric-react/lib/DocumentCard';
5454
import { ImageFit } from 'office-ui-fabric-react/lib/Image';
5555
import { FilePicker, IFilePickerResult } from '../../../FilePicker';
56-
import { FolderExplorer, IFolder } from '../../../FolderExplorer';
56+
import { FolderExplorer, IFolder } from '../../../controls/folderExplorer';
57+
import FolderPicker from '../../../controls/folderPicker/FolderPicker';
5758

5859
/**
5960
* The sample data below was randomly generated (except for the title). It is used by the grid layout
@@ -330,6 +331,11 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
330331
}
331332
}
332333

334+
private _onFolderSelect = (folder: IFolder): void => {
335+
console.log('selected folder', folder);
336+
337+
}
338+
333339
private _onRenderGridItem = (item: any, _finalSize: ISize, isCompact: boolean): JSX.Element => {
334340
const previewProps: IDocumentCardPreviewProps = {
335341
previewImages: [
@@ -711,14 +717,14 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
711717
<div className="ms-font-m">ComboBoxListItemPicker:
712718

713719
<ComboBoxListItemPicker listId={'0ffa51d7-4ad1-4f04-8cfe-98209905d6da'}
714-
columnInternalName='Title'
715-
keyColumnInternalName='Id'
716-
multiSelect={true}
717-
onSelectedItem={(data) => {
718-
console.log(`Item(s):`, data);
719-
}}
720-
webUrl={this.props.context.pageContext.web.absoluteUrl}
721-
spHttpClient={this.props.context.spHttpClient} />
720+
columnInternalName='Title'
721+
keyColumnInternalName='Id'
722+
multiSelect={true}
723+
onSelectedItem={(data) => {
724+
console.log(`Item(s):`, data);
725+
}}
726+
webUrl={this.props.context.pageContext.web.absoluteUrl}
727+
spHttpClient={this.props.context.spHttpClient} />
722728

723729
</div>
724730

@@ -836,6 +842,18 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
836842
iframeOnLoad={(iframe: any) => { console.log('iframe loaded'); }}
837843
/>
838844
</div>
845+
<div>
846+
<FolderPicker context={this.props.context}
847+
rootFolder={{
848+
Name: 'Documents',
849+
ServerRelativeUrl: `${this.props.context.pageContext.web.serverRelativeUrl === '/' ? '' : this.props.context.pageContext.web.serverRelativeUrl}/Shared Documents`
850+
}}
851+
onSelect={this._onFolderSelect}
852+
label='Pick a folder'
853+
required={true}
854+
canCreateFolders={true}
855+
></FolderPicker>
856+
</div>
839857
</div>
840858
</div>
841859
</div>
@@ -917,27 +935,23 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
917935
/>
918936

919937
<div>
920-
<FolderExplorer
921-
context={this.props.context}
922-
rootFolder={{
923-
Name: 'Documents',
924-
ServerRelativeUrl: `${this.props.context.pageContext.web.serverRelativeUrl === '/' ? '' : this.props.context.pageContext.web.serverRelativeUrl}/Shared Documents`
925-
}}
926-
defaultFolder={{
927-
Name: 'Documents',
928-
ServerRelativeUrl: `${this.props.context.pageContext.web.serverRelativeUrl === '/' ? '' : this.props.context.pageContext.web.serverRelativeUrl}/Shared Documents`
929-
}}
930-
onSelect={this._onFolderSelect}
931-
canCreateFolders={true}
932-
/>
933-
</div>
938+
<FolderExplorer
939+
context={this.props.context}
940+
rootFolder={{
941+
Name: 'Documents',
942+
ServerRelativeUrl: `${this.props.context.pageContext.web.serverRelativeUrl === '/' ? '' : this.props.context.pageContext.web.serverRelativeUrl}/Shared Documents`
943+
}}
944+
defaultFolder={{
945+
Name: 'Documents',
946+
ServerRelativeUrl: `${this.props.context.pageContext.web.serverRelativeUrl === '/' ? '' : this.props.context.pageContext.web.serverRelativeUrl}/Shared Documents`
947+
}}
948+
onSelect={this._onFolderSelect}
949+
canCreateFolders={true}
950+
/>
951+
</div>
952+
934953
</div>
935954
);
936955
}
937956

938-
private _onFolderSelect = (folder: IFolder): void => {
939-
console.log('selected folder', folder);
940-
941-
}
942-
943957
}

0 commit comments

Comments
 (0)