Skip to content

Commit d6442d9

Browse files
committed
Merge branch 'ltdu-fix-list-item-attachment' into dev
2 parents 8387b0d + af02d07 commit d6442d9

File tree

2 files changed

+95
-54
lines changed

2 files changed

+95
-54
lines changed

src/controls/listItemAttachments/ListItemAttachments.tsx

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
2222
import utilities from './utilities';
2323
import { Placeholder } from "../placeholder";
2424

25+
interface IPreviewImageCollection {
26+
[fileName: string]: IDocumentCardPreviewImage;
27+
}
28+
2529
export class ListItemAttachments extends React.Component<IListItemAttachmentsProps, IListItemAttachmentsState> {
2630
private _spservice: SPservice;
27-
private previewImages: IDocumentCardPreviewImage[];
31+
private previewImages: IPreviewImageCollection;
2832
private _utilities: utilities;
2933

3034
constructor(props: IListItemAttachmentsProps) {
@@ -53,45 +57,54 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
5357
this.loadAttachments();
5458
}
5559

60+
private async loadAttachmentPreview(file: IListItemAttachmentFile): Promise<IDocumentCardPreviewImage> {
61+
return this._utilities.GetFileImageUrl(file).then(previewImageUrl => {
62+
return {
63+
name: file.FileName,
64+
previewImageSrc: previewImageUrl,
65+
iconSrc: '',
66+
imageFit: ImageFit.center,
67+
width: 187,
68+
height: 130,
69+
};
70+
});
71+
}
72+
5673
/**
5774
* Load Item Attachments
5875
*/
5976
private async loadAttachments() {
60-
this.previewImages = [];
61-
try {
62-
const files: IListItemAttachmentFile[] = await this._spservice.getListItemAttachments(this.props.listId, this.props.itemId);
63-
64-
for (const file of files) {
65-
const _previewImage = await this._utilities.GetFileImageUrl(file);
66-
this.previewImages.push({
67-
name: file.FileName,
68-
previewImageSrc: _previewImage,
69-
iconSrc: '',
70-
imageFit: ImageFit.center,
71-
width: 187,
72-
height: 130,
77+
this._spservice.getListItemAttachments(this.props.listId, this.props.itemId).then((files: IListItemAttachmentFile[]) => {
78+
const filePreviewImages = files.map(file => this.loadAttachmentPreview(file));
79+
return Promise.all(filePreviewImages).then(filePreviews => {
80+
this.previewImages = {};
81+
filePreviews.forEach(preview => {
82+
this.previewImages[preview.name] = preview;
7383
});
74-
}
7584

76-
this.setState({
77-
hideDialog: true,
78-
dialogMessage: '',
79-
attachments: files,
80-
showPlaceHolder: files.length === 0 ? true : false
85+
this.setState({
86+
fireUpload: false,
87+
hideDialog: true,
88+
dialogMessage: '',
89+
attachments: files,
90+
showPlaceHolder: files.length === 0 ? true : false
91+
});
8192
});
82-
} catch (error) {
93+
}).catch((error: Error) => {
8394
this.setState({
84-
hideDialog: true,
95+
fireUpload: false,
96+
hideDialog: false,
8597
dialogMessage: strings.ListItemAttachmentserrorLoadAttachments.replace('{0}', error.message)
8698
});
87-
}
99+
});
88100
}
89101

90102
/**
91103
* Close the dialog
92104
*/
93105
private _closeDialog = () => {
94106
this.setState({
107+
fireUpload: false,
95108
hideDialog: true,
96109
dialogMessage: '',
97110
file: null,
@@ -116,6 +129,7 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
116129
*/
117130
private onDeleteAttachment = (file: IListItemAttachmentFile) => {
118131
this.setState({
132+
fireUpload: false,
119133
hideDialog: false,
120134
deleteAttachment: true,
121135
file: file,
@@ -131,13 +145,15 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
131145
const { file } = this.state;
132146

133147
this.setState({
148+
fireUpload: false,
134149
disableButton: true,
135150
});
136151

137152
try {
138153
await this._spservice.deleteAttachment(file.FileName, this.props.listId, this.props.itemId, this.props.webUrl);
139154

140155
this.setState({
156+
fireUpload: false,
141157
hideDialog: false,
142158
deleteAttachment: false,
143159
disableButton: false,
@@ -146,6 +162,7 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
146162
});
147163
} catch (error) {
148164
this.setState({
165+
fireUpload: false,
149166
hideDialog: false,
150167
file: null,
151168
deleteAttachment: false,
@@ -179,22 +196,22 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
179196
onConfigure={() => this.setState({ fireUpload: true })} />
180197
:
181198

182-
this.state.attachments.map((file, i: number) => {
199+
this.state.attachments.map(file => {
200+
const fileName = file.FileName;
201+
const previewImage = this.previewImages[fileName];
183202
return (
184-
<div className={styles.documentCardWrapper}>
203+
<div key={fileName} className={styles.documentCardWrapper}>
185204
<TooltipHost
186-
content={file.FileName}
205+
content={fileName}
187206
calloutProps={{ gapSpace: 0, isBeakVisible: true }}
188207
closeDelay={200}
189208
directionalHint={DirectionalHint.rightCenter}>
190209

191210
<DocumentCard
192211
onClickHref={`${file.ServerRelativeUrl}?web=1`}
193212
className={styles.documentCard}>
194-
<DocumentCardPreview previewImages={[this.previewImages[i]]} />
195-
<Label className={styles.fileLabel}>
196-
{file.FileName}
197-
</Label>
213+
<DocumentCardPreview previewImages={[previewImage]} />
214+
<Label className={styles.fileLabel}>{fileName}</Label>
198215
<DocumentCardActions
199216
actions={
200217
[

src/services/SPService.ts

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ISPService, ILibsOptions, LibsOrderBy } from "./ISPService";
22
import { ISPLists } from "../common/SPEntities";
33
import { WebPartContext } from "@microsoft/sp-webpart-base";
44
import { ExtensionContext } from "@microsoft/sp-extension-base";
5-
import { SPHttpClient, SPHttpClientResponse,ISPHttpClientOptions } from "@microsoft/sp-http";
5+
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from "@microsoft/sp-http";
66
import { sp, Web } from '@pnp/sp';
77

88
export default class SPService implements ISPService {
@@ -63,24 +63,25 @@ export default class SPService implements ISPService {
6363
}
6464
}
6565

66-
/**
67-
* Get list item attachments
68-
*
69-
* @param listId
70-
* @param itemId
71-
* @param webUrl
72-
*/
73-
public async getListItemAttachments(listId: string, itemId: number, webUrl?: string): Promise<any[]> {
66+
/**
67+
* Get list item attachments
68+
*
69+
* @param listId
70+
* @param itemId
71+
* @param webUrl
72+
*/
73+
public async getListItemAttachments(listId: string, itemId: number, webUrl?: string): Promise<any[]> {
7474
try {
7575
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
76-
const apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items(${itemId})/AttachmentFiles`;
76+
const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(@itemId)/AttachmentFiles?@listId=guid'${encodeURIComponent(listId)}'&@itemId=${encodeURIComponent(String(itemId))}`;
7777
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
7878
if (data.ok) {
7979
const results = await data.json();
80-
if (results && results.value && results.value.length > 0) {
80+
if (results && results.value) {
8181
return results.value;
8282
}
8383
}
84+
8485
return null;
8586
} catch (error) {
8687
console.dir(error);
@@ -102,7 +103,7 @@ export default class SPService implements ISPService {
102103
headers: { "X-HTTP-Method": 'DELETE', }
103104
};
104105
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
105-
const apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items(${itemId})/AttachmentFiles/getByFileName('${encodeURIComponent(fileName)}')`;
106+
const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(@itemId)/AttachmentFiles/getByFileName(@fileName)?@listId=guid'${encodeURIComponent(listId)}'&@itemId=${encodeURIComponent(String(itemId))}&@fileName='${encodeURIComponent(fileName.replace(/'/g, "''"))}'`;
106107
const data = await this._context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, spOpts);
107108
} catch (error) {
108109
console.dir(error);
@@ -121,20 +122,20 @@ export default class SPService implements ISPService {
121122
*/
122123
public async addAttachment(listId: string, itemId: number, fileName: string, file: File, webUrl?: string): Promise<void> {
123124
try {
124-
// remove special characteres in FileName
125+
// Remove special characters in FileName
125126
fileName = fileName.replace(/[^\.\w\s]/gi, '');
126-
// Check if Attachment Exists
127+
// Check if attachment exists
127128
const fileExists = await this.checkAttachmentExists(listId, itemId, fileName, webUrl);
128-
// Delete Attachment if exists
129+
// Delete attachment if it exists
129130
if (fileExists) {
130131
await this.deleteAttachment(fileName, listId, itemId, webUrl);
131132
}
132-
// Add Attachment
133+
// Add attachment
133134
const spOpts: ISPHttpClientOptions = {
134135
body: file
135136
};
136137
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
137-
const apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items(${itemId})/AttachmentFiles/add(FileName='${encodeURIComponent(fileName)}')`;
138+
const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(@itemId)/AttachmentFiles/add(FileName=@fileName)?@listId=guid'${encodeURIComponent(listId)}'&@itemId=${encodeURIComponent(String(itemId))}&@fileName='${encodeURIComponent(fileName.replace(/'/g, "''"))}'`;
138139
const data = await this._context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, spOpts);
139140
return;
140141
} catch (error) {
@@ -152,14 +153,15 @@ export default class SPService implements ISPService {
152153
*/
153154
public async getAttachment(listId: string, itemId: number, fileName: string, webUrl?: string): Promise<any> {
154155
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
155-
const apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')/items(${itemId})/AttachmentFiles/GetByFileBame('${fileName}'))`;
156+
const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items(@itemId)/AttachmentFiles/GetByFileBame(@fileName))?@listId=guid'${encodeURIComponent(listId)}'&@itemId=${encodeURIComponent(String(itemId))}&@fileName='${encodeURIComponent(fileName.replace(/'/g, "''"))}'`;
156157
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
157158
if (data.ok) {
158159
const file = await data.json();
159160
if (file) {
160161
return file;
161162
}
162163
}
164+
163165
return null;
164166
}
165167

@@ -173,14 +175,15 @@ export default class SPService implements ISPService {
173175
*/
174176
public async checkAttachmentExists(listId: string, itemId: number, fileName: string, webUrl?: string): Promise<any> {
175177
try {
176-
const listName = await this.getListName(listId, webUrl);
178+
const listServerRelativeUrl = await this.getListServerRelativeUrl(listId, webUrl);
177179
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
178-
const apiUrl = `${webAbsoluteUrl}/_api/web/getfilebyserverrelativeurl('/lists/${listName}/Attachments/${itemId}/${fileName}')`;
180+
const fileServerRelativeUrl = `${listServerRelativeUrl}/Attachments/${itemId}/${fileName}`;
181+
const apiUrl = `${webAbsoluteUrl}/_api/web/getfilebyserverrelativeurl(@url)/Exists?@url='${encodeURIComponent(fileServerRelativeUrl.replace(/'/g, "''"))}'`;
179182
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
180183
if (data.ok) {
181184
const results = await data.json();
182-
if (results && results.Exists) {
183-
return results.Exists;
185+
if (results) {
186+
return results.value;
184187
}
185188
}
186189

@@ -198,14 +201,35 @@ export default class SPService implements ISPService {
198201
*/
199202
public async getListName(listId: string, webUrl?: string): Promise<string> {
200203
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
201-
const apiUrl = `${webAbsoluteUrl}/_api/web/lists('${listId}')?$select=RootFolder/Name&$expand=RootFolder)`;
204+
const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/RootFolder/Name?@listId=guid'${encodeURIComponent(listId)}'`;
202205
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
203206
if (data.ok) {
204207
const results = await data.json();
205208
if (results) {
206-
return results.RootFolder.Name;
209+
return results.value;
207210
}
208211
}
212+
213+
return;
214+
}
215+
216+
/**
217+
* Get the list server relative url
218+
*
219+
* @param listId
220+
* @param webUrl
221+
*/
222+
public async getListServerRelativeUrl(listId: string, webUrl?: string): Promise<string> {
223+
const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl;
224+
const apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/RootFolder/ServerRelativeUrl?@listId=guid'${encodeURIComponent(listId)}'`;
225+
const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
226+
if (data.ok) {
227+
const results = await data.json();
228+
if (results) {
229+
return results.value;
230+
}
231+
}
232+
209233
return;
210234
}
211235
}

0 commit comments

Comments
 (0)