Skip to content

Commit 70c3513

Browse files
committed
2 parents 4fbaa4b + c90a3c0 commit 70c3513

16 files changed

+707
-94
lines changed

CHANGELOG.JSON

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"`fast-serve`: Add fast-serve support [#916](https://github.com/pnp/sp-dev-fx-controls-react/pull/916)",
1212
"`ComboBoxListItemPicker` and `ListItemPicker`: Add label to control [#914](https://github.com/pnp/sp-dev-fx-controls-react/pull/914)",
1313
"`PeoplePicker`: new property `groupId`. [#917](https://github.com/pnp/sp-dev-fx-controls-react/pull/917)",
14-
"`ListPicker`: add contenttype id to list picker [#894](https://github.com/pnp/sp-dev-fx-controls-react/issues/894)"
14+
"`ListPicker`: add contenttype id to list picker [#894](https://github.com/pnp/sp-dev-fx-controls-react/issues/894)",
15+
"`ListPicker`: Few more tests with a little better description [#906](https://github.com/pnp/sp-dev-fx-controls-react/pull/906)"
1516
],
1617
"fixes": [
1718
"Documentation for `RichText`: correct event handler name [#898](https://github.com/pnp/sp-dev-fx-controls-react/pull/898)",
@@ -30,7 +31,8 @@
3031
"[Ravichandran Krishnasamy](https://github.com/ravichandran-blog)",
3132
"[Russell gove](https://github.com/russgove)",
3233
"[Sergei Sergeev](https://github.com/s-KaiNet)",
33-
"[João Mendes](https://github.com/joaojmendes)"
34+
"[João Mendes](https://github.com/joaojmendes)",
35+
"[Marcin Wojciechowski](https://github.com/mgwojciech)"
3436
]
3537
},
3638
{

docs/documentation/docs/controls/ListItemAttachments.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ import { ListItemAttachments } from '@pnp/spfx-controls-react/lib/ListItemAttach
2929
disabled={false} />
3030
```
3131

32+
- If You want to use `ListItemAttachments` controls with new form You have to use React.createRef.
33+
34+
Following example will add selected attachments to list item with id = 1
35+
36+
```TypeScript
37+
let listItemAttachmentsComponentReference = React.createRef<ListItemAttachments>();
38+
...
39+
<ListItemAttachments
40+
ref={listItemAttachmentsComponentReference}
41+
context={this.props.context}
42+
listId="dfcfdb95-2488-4757-b55b-14d94166ad87"
43+
itemId={0} />
44+
...
45+
<PrimaryButton text="Save to Item with id 1" onClick={()=>{
46+
//@ts-ignore
47+
listItemAttachmentsComponentReference.current.uploadAttachments(1);
48+
}} />
49+
```
50+
3251
## Implementation
3352

3453
The `ListItemAttachments` control can be configured with the following properties:
@@ -37,7 +56,7 @@ The `ListItemAttachments` control can be configured with the following propertie
3756
| Property | Type | Required | Description |
3857
| ---- | ---- | ---- | ---- |
3958
| context | BaseComponentContext | yes | SPFx web part or extention context |
40-
| itemId | number | yes | List Item Id |
59+
| itemId | number | no | List Item Id |
4160
| listId | string | yes | Guid of the list. |
4261
| webUrl | string | no | URL of the site. By default it uses the current site URL. |
4362
| disabled | boolean | no | Specifies if the control is disabled or not. |

package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"build": "gulp build",
77
"clean": "gulp clean",
88
"test": "gulp test",
9+
"test:unit": "npx jest --silent --maxWorkers=4",
910
"serve": "gulp bundle --custom-serve --max_old_space_size=4096 && fast-serve",
1011
"prepublishOnly": "gulp",
1112
"versionUpdater": "gulp versionUpdater",
@@ -126,27 +127,8 @@
126127
"@microsoft/sp-webpart-base": "identity-obj-proxy",
127128
"@microsoft/sp-core-library": "identity-obj-proxy",
128129
"@microsoft/sp-application-base": "identity-obj-proxy",
129-
"office-ui-fabric-react/lib/FocusZone": "identity-obj-proxy",
130-
"office-ui-fabric-react/lib/List": "identity-obj-proxy",
131-
"office-ui-fabric-react/lib/Spinner": "identity-obj-proxy",
132-
"office-ui-fabric-react/lib/Image": "identity-obj-proxy",
133-
"office-ui-fabric-react/lib/Button": "identity-obj-proxy",
134-
"office-ui-fabric-react/lib/components/Button": "identity-obj-proxy",
135-
"office-ui-fabric-react/lib/Selection": "identity-obj-proxy",
136-
"office-ui-fabric-react/lib/Icon": "identity-obj-proxy",
137-
"office-ui-fabric-react/lib/Styling": "identity-obj-proxy",
138-
"office-ui-fabric-react/lib/Check": "identity-obj-proxy",
139-
"office-ui-fabric-react/lib/DetailsList": "identity-obj-proxy",
140-
"office-ui-fabric-react/lib/CommandBar": "identity-obj-proxy",
141-
"office-ui-fabric-react/lib/ContextualMenu": "identity-obj-proxy",
142-
"office-ui-fabric-react/lib/ScrollablePane": "identity-obj-proxy",
143-
"office-ui-fabric-react/lib/Breadcrumb": "identity-obj-proxy",
144-
"office-ui-fabric-react/lib/Link": "identity-obj-proxy",
145-
"office-ui-fabric-react/lib/Dialog": "identity-obj-proxy",
146-
"office-ui-fabric-react/lib/common/DirectionalHint": "identity-obj-proxy",
147-
"office-ui-fabric-react/lib/Persona": "identity-obj-proxy",
148-
"office-ui-fabric-react/lib/HoverCard": "identity-obj-proxy",
149-
"office-ui-fabric-react/lib/components/Icon": "identity-obj-proxy",
130+
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1",
131+
"src/common/telemetry/(.*)$":"identity-obj-proxy",
150132
"@pnp/sp": "identity-obj-proxy",
151133
"'@pnp/sp/fields": "identity-obj-proxy",
152134
"ControlStrings": "identity-obj-proxy",

src/controls/listItemAttachments/IListItemAttachmentsProps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BaseComponentContext } from '@microsoft/sp-component-base';
22

33
export interface IListItemAttachmentsProps {
44
listId: string;
5-
itemId: number;
5+
itemId?: number;
66
className?: string;
77
webUrl?:string;
88
disabled?: boolean;

src/controls/listItemAttachments/IListItemAttachmentsState.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ export interface IListItemAttachmentsState {
88
disableButton: boolean;
99
showPlaceHolder: boolean;
1010
fireUpload: boolean;
11+
filesToUpload?: File[];
12+
itemId?: number;
1113
}

src/controls/listItemAttachments/IUploadAttachmentProps.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { BaseComponentContext } from '@microsoft/sp-component-base';
22

33
export interface IUploadAttachmentProps {
44
listId: string;
5-
itemId: number;
5+
itemId?: number;
66
className?: string;
7-
webUrl?:string;
7+
webUrl?: string;
88
disabled?: boolean;
99
context: BaseComponentContext;
10-
fireUpload?:boolean;
11-
onAttachmentUpload: () => void;
10+
fireUpload?: boolean;
11+
onAttachmentUpload: (file?: File) => void;
1212
}

src/controls/listItemAttachments/ListItemAttachments.tsx

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@ interface IPreviewImageCollection {
2929

3030
export class ListItemAttachments extends React.Component<IListItemAttachmentsProps, IListItemAttachmentsState> {
3131
private _spservice: SPservice;
32-
private previewImages: IPreviewImageCollection;
32+
private previewImages: IPreviewImageCollection = {};
3333
private _utilities: utilities;
3434

3535
constructor(props: IListItemAttachmentsProps) {
3636
super(props);
3737

3838
telemetry.track('ReactListItemAttachments', {});
39-
4039
this.state = {
4140
file: null,
4241
hideDialog: true,
@@ -45,7 +44,9 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
4544
deleteAttachment: false,
4645
disableButton: false,
4746
showPlaceHolder: false,
48-
fireUpload: false
47+
fireUpload: false,
48+
filesToUpload: [],
49+
itemId: props.itemId
4950
};
5051

5152
// Get SPService Factory
@@ -56,8 +57,8 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
5657
/**
5758
* componentDidMount lifecycle hook
5859
*/
59-
public componentDidMount() {
60-
this.loadAttachments();
60+
public async componentDidMount() {
61+
await this.loadAttachments();
6162
}
6263

6364
private async loadAttachmentPreview(file: IListItemAttachmentFile): Promise<IDocumentCardPreviewImage> {
@@ -73,34 +74,69 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
7374
});
7475
}
7576

76-
/**
77-
* Load Item Attachments
78-
*/
79-
private async loadAttachments() {
80-
this._spservice.getListItemAttachments(this.props.listId, this.props.itemId).then((files: IListItemAttachmentFile[]) => {
81-
const filePreviewImages = files.map(file => this.loadAttachmentPreview(file));
82-
return Promise.all(filePreviewImages).then(filePreviews => {
83-
this.previewImages = {};
84-
filePreviews.forEach(preview => {
85-
this.previewImages[preview.name] = preview;
86-
});
77+
public async uploadAttachments(itemId: number){
78+
if(this.state.filesToUpload){
79+
await Promise.all(this.state.filesToUpload.map(file=>this._spservice.addAttachment(
80+
this.props.listId,
81+
itemId,
82+
file.name,
83+
file,
84+
this.props.webUrl)));
85+
}
86+
return new Promise<void>((resolve,error)=>{
87+
this.setState({
88+
filesToUpload: [],
89+
itemId: itemId
90+
},()=>this.loadAttachments().then(resolve));
91+
});
92+
}
93+
protected loadAttachmentsPreview(files: IListItemAttachmentFile[]){
94+
const filePreviewImages = files.map(file => this.loadAttachmentPreview(file));
95+
return Promise.all(filePreviewImages).then(filePreviews => {
96+
filePreviews.forEach(preview => {
97+
this.previewImages[preview.name] = preview;
98+
});
8799

88-
this.setState({
100+
this.setState({
89101
fireUpload: false,
90102
hideDialog: true,
91103
dialogMessage: '',
92104
attachments: files,
93105
showPlaceHolder: files.length === 0 ? true : false
94106
});
107+
});
108+
}
109+
/**
110+
* Load Item Attachments
111+
*/
112+
private async loadAttachments() {
113+
if(this.state.itemId){
114+
await this._spservice.getListItemAttachments(this.props.listId, this.state.itemId).then(async (files: IListItemAttachmentFile[]) => {
115+
await this.loadAttachmentsPreview(files);
116+
}).catch((error: Error) => {
117+
this.setState({
118+
fireUpload: false,
119+
hideDialog: false,
120+
dialogMessage: strings.ListItemAttachmentserrorLoadAttachments.replace('{0}', error.message)
121+
});
95122
});
96-
}).catch((error: Error) => {
97-
this.setState({
98-
fireUpload: false,
99-
hideDialog: false,
100-
dialogMessage: strings.ListItemAttachmentserrorLoadAttachments.replace('{0}', error.message)
101-
});
123+
}
124+
else if(this.state.filesToUpload && this.state.filesToUpload.length > 0){
125+
let files = this.state.filesToUpload.map(file=>({
126+
FileName: file.name,
127+
ServerRelativeUrl: undefined
128+
}));
129+
await this.loadAttachmentsPreview(files);
130+
}
131+
else{
132+
this.setState({
133+
fireUpload: false,
134+
hideDialog: true,
135+
dialogMessage: '',
136+
showPlaceHolder: true
102137
});
103138
}
139+
}
104140

105141
/**
106142
* Close the dialog
@@ -120,9 +156,16 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
120156
/**
121157
* Attachment uploaded event handler
122158
*/
123-
private _onAttachmentUpload = () => {
159+
private _onAttachmentUpload = async (file: File) => {
124160
// load Attachments
125-
this.loadAttachments();
161+
if(!this.state.itemId){
162+
let files = this.state.filesToUpload || [];
163+
files.push(file);
164+
this.setState({
165+
filesToUpload: [...files]
166+
});
167+
}
168+
await this.loadAttachments();
126169
}
127170

128171
/**
@@ -153,8 +196,26 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
153196
});
154197

155198
try {
156-
await this._spservice.deleteAttachment(file.FileName, this.props.listId, this.props.itemId, this.props.webUrl);
199+
if(this.state.itemId){
200+
await this._spservice.deleteAttachment(file.FileName, this.props.listId, this.state.itemId, this.props.webUrl);
201+
}
202+
else{
203+
let filesToUpload = this.state.filesToUpload;
204+
let fileToRemove = filesToUpload.find(f=>f.name === file.FileName);
205+
if(fileToRemove){
206+
filesToUpload.splice(filesToUpload.indexOf(fileToRemove),1);
207+
}
208+
let attachments = this.state.attachments;
209+
let attachmentToRemove = attachments.find(attachment => attachment.FileName === file.FileName);
210+
if(attachmentToRemove){
211+
attachments.splice(attachments.indexOf(attachmentToRemove),1);
157212

213+
}
214+
this.setState({
215+
filesToUpload: [...filesToUpload],
216+
attachments: [...attachments]
217+
});
218+
}
158219
this.setState({
159220
fireUpload: false,
160221
hideDialog: false,
@@ -183,7 +244,7 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
183244
<div className={styles.ListItemAttachments}>
184245
<UploadAttachment
185246
listId={this.props.listId}
186-
itemId={this.props.itemId}
247+
itemId={this.state.itemId}
187248
disabled={this.props.disabled}
188249
context={this.props.context}
189250
onAttachmentUpload={this._onAttachmentUpload}

src/controls/listItemAttachments/UploadAttachment.tsx

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,34 @@ export class UploadAttachment extends React.Component<IUploadAttachmentProps, IU
6060

6161
const reader = new FileReader();
6262
const file = e.target.files[0];
63-
64-
reader.onloadend = async () => {
65-
this.setState({
66-
file: file
67-
});
68-
69-
try {
70-
await this._spservice.addAttachment(this.props.listId, this.props.itemId, file.name, file, this.props.webUrl);
71-
72-
this.setState({
73-
isLoading: false
74-
});
75-
this.props.onAttachmentUpload();
76-
} catch (error) {
63+
return new Promise<void>((resolve,errorCallback)=>{
64+
reader.onloadend = async () => {
7765
this.setState({
78-
hideDialog: false,
79-
isLoading: false,
80-
dialogMessage: strings.ListItemAttachmentsuploadAttachmentErrorMsg.replace('{0}', file.name).replace('{1}', error.message)
66+
file: file
8167
});
82-
}
83-
};
84-
reader.readAsDataURL(file);
68+
69+
try {
70+
if(this.props.itemId && this.props.itemId > 0){
71+
await this._spservice.addAttachment(this.props.listId, this.props.itemId, file.name, file, this.props.webUrl);
72+
}
73+
74+
this.setState({
75+
isLoading: false
76+
});
77+
this.props.onAttachmentUpload(file);
78+
resolve();
79+
} catch (error) {
80+
this.setState({
81+
hideDialog: false,
82+
isLoading: false,
83+
dialogMessage: strings.ListItemAttachmentsuploadAttachmentErrorMsg.replace('{0}', file.name).replace('{1}', error.message)
84+
});
85+
errorCallback(error);
86+
}
87+
};
88+
reader.readAsDataURL(file);
89+
});
90+
8591
}
8692

8793
/**

tests/controls/documentLibraryBrowser/DocumentLibraryBrowser.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe("<DocumentLibraryBrowser />", ()=>{
2626

2727
}}
2828
/>);
29-
assert.equal(documentLibraryBrowser.getDOMNode().tagName, "SPINNER");
29+
assert.equal(documentLibraryBrowser.getDOMNode().tagName, "DIV");
3030

3131
await documentLibraryBrowser.instance().componentDidMount();
3232
documentLibraryBrowser.update();
@@ -53,8 +53,8 @@ describe("<DocumentLibraryBrowser />", ()=>{
5353
let libraryTitle = documentLibraryBrowser.instance()._onRenderLibraryTile(browserService.getSiteMediaLibrariesResult[0],0);
5454
let iconControl = libraryTitle.props.children.props.children.props.children[0];
5555
let buttonControl = libraryTitle.props.children.props.children.props.children[1];
56-
assert.equal(iconControl.type,"Image");
57-
assert.equal(buttonControl.type,"DefaultButton");
56+
assert.equal(iconControl.type.displayName,"StyledImageBase");
57+
assert.equal(buttonControl.type.displayName,"CustomizedDefaultButton");
5858
});
5959
test("should call onOpenLibrary", async ()=>{
6060
let asserted = false;

0 commit comments

Comments
 (0)