Open walleford opened 2 years ago
Thank you for reporting this issue. We will be triaging your incoming issue as soon as possible.
The code did not format properly:
import * as React from 'react';
import styles from './CallingCards.module.scss';
import { ICallingCardsProps } from './ICallingCardsProps';
import { escape } from '@microsoft/sp-lodash-subset';
export default class CallingCards extends React.Component<ICallingCardsProps, {}> {
public render(): React.ReactElement<ICallingCardsProps> {
if (this.props.CallingCards && this.props.CallingCards.length > 0) {
var contacts = this.props.CallingCards.map(el =>
<div className={`${styles.tile}`}>
<div className={`${styles.galleryframe}`}>
<img className={`${styles.galleryimg}`} key={el} src={this.props.CallingCards && this.props.CallingCards.length > 0 ? this.props.CallingCards[el].filePicker.fileAbsoluteUrl : ''} />
<div key={el}>{el.Name}</div>
<div key={el}>{el.Email}</div>
<div key={el}>{el.PhoneNumber}</div>
</div>
</div>
)
} else {
return (
<div className={`${styles.welcome}`}>Use property pane to create new Contact Cards!</div>
)
}
return (
<body style={{ height: '225px' }}>
<div className={`${styles.grid}`}>
{contacts}
</div>
</body>
);
}
}
PropertyFieldCollectionData('CallingCards', {
key: 'CallingCards',
label: 'Contact Card Information',
panelHeader: 'Contact Card Panel',
manageBtnLabel: 'Manage Contact Cards',
value: this.properties.CallingCards,
fields: [
{
id: 'Name',
title: 'Contact Name',
type: CustomCollectionFieldType.string,
required: true
},
{
id: 'Email',
title: 'Contact Email',
type: CustomCollectionFieldType.string,
required: true
},
{
id: 'PhoneNumber',
title: 'Contact Phone Number',
type: CustomCollectionFieldType.string,
required: false
},
{
id: "filePicker",
title: "Select File",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(FilePicker, {
key: itemId,
context: this.context,
buttonLabel: "Select File",
onChange: (filePickerResult: IFilePickerResult[]) => {
console.log('changing....', field);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
},
onSave: (filePickerResult: IFilePickerResult[]) => {
console.log('saving....', field);
if (filePickerResult && filePickerResult.length > 0) {
console.log('filePickerResult && filePickerResult.length > 0');
if (filePickerResult[0].fileAbsoluteUrl == null) {
console.log('filePickerResult[0].fileAbsoluteUrl == null');
filePickerResult[0].downloadFileContent().then(async r => {
let fileresult = await this.web.getFolderByServerRelativeUrl(`${this.context.pageContext.site.serverRelativeUrl}/SiteAssets/SitePages`).files.addUsingPath(filePickerResult[0].fileName, r, true);
filePickerResult[0].fileAbsoluteUrl = `${this.context.pageContext.site.absoluteUrl}/SiteAssets/SitePages/${fileresult.data.Name}`;
console.log('saving....', filePickerResult[0]);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
});
} else {
console.log('saving....', filePickerResult[0]);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
}
}
},
hideLocalUploadTab: false,
hideLocalMultipleUploadTab: true,
hideLinkUploadTab: false
})
);
},
required: true
},
],
disabled: false,
}),
@walleford I know it's a bit late so assume you have already resolved the problem? If not, are you able to debug and try to identify the line where the error happens?
@joelfmrodrigues Thank you for the response, I was able to figure this particular issue out, however, I am still struggling with getting uploaded pictures to render. I don't believe my onSave it saving the files to sharepoint so they can be rendered. Do you have any guidance on where I can figure out how to do this or know of what I can do?
Hi @walleford
Copying this from a recent project where we used the control, hope it helps
Control (onSave can also be declared as a separate function if you prefer):
<FilePicker
label={!props.extended && strings.DocumentsLabel}
buttonLabel={strings.AddDocumentsLabel}
buttonIcon="Add"
onSave={async (filesPicked: IFilePickerResult[]) => {
for (const filePicked of filesPicked) {
const filePickedContent = await filePicked.downloadFileContent();
uploadFiles(filePickedContent, filePicked.fileName);
}
}}
context={webPartContext}
hideSiteFilesTab
hideStockImages
hideWebSearchTab
hideOneDriveTab
hideRecentTab
hideLinkUploadTab
hideOrganisationalAssetTab
disabled={
documentsQueryStatus.loading ||
props.disabled
}
/>
For uploading the content using PnPjs:
try {
// small upload
if (fileContent.size <= 10485760) {
const uploaded = await sp.web.getFolderByServerRelativeUrl(FolderPath)
.usingCaching()
.files.add(fileName, fileContent, true);
return Promise.resolve(true);
}
// large upload
else {
const uploaded = await sp.web.getFolderByServerRelativeUrl(FolderPath).usingCaching()
.files.addChunked(
fileName,
fileContent,
(data) => {
},
true
);
return Promise.resolve(true);
}
}
catch (err) {
// (error handling removed for simplicity)
return Promise.resolve(false);
}
Hello,
Thank you for that. Could you point me to the webpart utilizing it? I would like to see the code so I can understand better what is exactly happening.
Sorry, this is a private web part from a client, not a public one.
Ah okay I see. The onSave function looks like it is calling an uploadFiles function and passing the filePickedContent and filePicked.fileName. Would you be able to share that?
Both properties (file content and file name) are then used in the block of code that I shared below. In my case, the solution is very complex and there is a lot of business logic going on between the two functions to manipulate data, which is why I didn't include that here, but in your case, you can just create a function uploadFiles
and use the second block of code there - adding any missing bits, like the FolderPath.
Also don't forget to initialize PnPjs as per the library documentation
PS: where I have return Promise.resolve(true);
, this was simplified without testing when copying here. In our case, we return a custom object with multiple properties that are required by our app so you can change it if needed or keep it as a boolean. Up to you
Hello,
I have set it up this way:
private async uploadFiles(fileContent, fileName, FolderPath) {
try {
if (fileContent.size <= 10485760) {
// small upload
let result = await this.sp.web.getFolderByServerRelativePath("Shared Documents").files.addUsingPath(FolderPath, fileContent.type, { Overwrite: true });
} else {
// large upload
let result = await this.sp.web.getFolderByServerRelativePath("Shared Documents").files.addChunked(FolderPath, fileContent.type, data => {
console.log(`progress`);
}, true);
}
}
catch (err) {
// (error handling removed for simplicity)
return Promise.resolve(false);
}
}
{
id: "filePicker",
title: "Select File",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(FilePicker, {
key: itemId,
context: this.context,
buttonLabel: "Select File",
onChange: (filePickerResult: IFilePickerResult[]) => {
console.log('changing....', field);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
},
onSave:
async (filesPicked: IFilePickerResult[]) => {
for (const filePicked of filesPicked) {
const filePickedContent = await filePicked.downloadFileContent();
const folderPath = await filePicked.fileAbsoluteUrl
this.uploadFiles(filePickedContent, filePicked.fileName, folderPath);
}
}
})
);
},
required: true
},
Yet when I upload an image, nothing happens. Is this being used the correct way?
@walleford sorry for the late reply, hope you had resolved the issue by now, but if not..
I can't really spot anything strange just by looking at the code. Are able to debug it and see where it may be failing?
Are you able to get the contents of the file with downloadFileContent()
?
Have you tried using a small and bigger file to test both cases inside the uploadFiles
function?
Are you sure PnPjs is correctly setup? Are you able to retrieve data from a list as an example?
Hey Joel, Thank you for taking time to help me out, I did not get it figured out yet. When I run the above code in my project this is the output from the browser console: While it is not rendering into the webpart:
Along with the above code I use this to initialize sp: private sp = spfi().using(SPFx(this.context));
While this is my .tsx code for rendering:
public render(): React.ReactElement<ICallingCardsProps> {
let arr = this.props.CallingCards || [];
console.log(this.props.CallingCards);
function handleClick(el): any {
window.open('mailto:' + el)
}
if (this.props.CallingCards && this.props.CallingCards.length > 0) {
var contactsPicture = arr.map(el =>
<div className={`${styles.tile}`}>
<img key={el} src={el.filePicker.fileAbsoluteUrl} />
<div className={`${styles.textContainer}`}>
<div className={`${styles.nameStyles}`} key={el}>{el.Name}</div>
<div className={`${styles.textStyles}`} key={el}>{el.Position}</div>
<div className={`${styles.textStyles}`} key={el}>Phone: {el.PhoneNumber}</div>
<div className={`${styles.textStyles}`} key={el}>Email: </div>
<a className={`${styles.emailStyles}`} href={`mailto:${el.Email}`} target="_top" key={el}>{el.Email}</a>
</div>
</div>
)
} else {
return (
<div className={`${styles.welcome}`}>Use property pane to create new Contact Cards!</div>
)
}
return (
<body>
<div className={`${styles.grid}`}>
{contactsPicture}
</div>
</body>
);
}
I am at a loss as to what could be causing this issue because I have probably read every "pnpfilepicker spfx" blog/post/article I can find, yet still don't have a working solution.
@joelfmrodrigues I wanted to include this: It does not seem like downloadFileContent is pulling the absolute URL for the image, which is what I am using to render it
Thank you for reporting an issue, suggesting an enhancement, or asking a question. We appreciate your feedback - to help the team understand your needs please complete the below template to ensure we have the details to help. Thanks!
Please check out the documentation to see if your question is already addressed there. This will help us ensure our documentation is up to date.
Category
Version
Please specify what version of the library you are using: [ 3.6.0 ]
/ Question
I am using the customCollectionFieldData property control, and using a custom field to import the FilePicker into it. When trying to render multiple items, I get Cannot Read Properties of Undefined. I wanted to ask if anyone can help explain what I am doing wrong to render this, as I am the only person using SPfx at my job. I will paste the property pane code and the tsx code in here:
`export default class CallingCards extends React.Component<ICallingCardsProps, {}> { public render(): React.ReactElement {
}`
PropertyFieldCollectionData('CallingCards', { key: 'CallingCards', label: 'Contact Card Information', panelHeader: 'Contact Card Panel', manageBtnLabel: 'Manage Contact Cards', value: this.properties.CallingCards, fields: [ { id: 'Name', title: 'Contact Name', type: CustomCollectionFieldType.string, required: true }, { id: 'Email', title: 'Contact Email', type: CustomCollectionFieldType.string, required: true }, { id: 'PhoneNumber', title: 'Contact Phone Number', type: CustomCollectionFieldType.string, required: false }, { id: "filePicker", title: "Select File", type: CustomCollectionFieldType.custom, onCustomRender: (field, value, onUpdate, item, itemId, onError) => { return ( React.createElement(FilePicker, { key: itemId, context: this.context, buttonLabel: "Select File", onChange: (filePickerResult: IFilePickerResult[]) => { console.log('changing....', field); onUpdate(field.id, filePickerResult[0]); this.context.propertyPane.refresh(); this.render(); }, onSave: (filePickerResult: IFilePickerResult[]) => { console.log('saving....', field); if (filePickerResult && filePickerResult.length > 0) { console.log('filePickerResult && filePickerResult.length > 0'); if (filePickerResult[0].fileAbsoluteUrl == null) { console.log('filePickerResult[0].fileAbsoluteUrl == null'); filePickerResult[0].downloadFileContent().then(async r => { let fileresult = await this.web.getFolderByServerRelativeUrl(
${this.context.pageContext.site.serverRelativeUrl}/SiteAssets/SitePages).files.addUsingPath(filePickerResult[0].fileName, r, true); filePickerResult[0].fileAbsoluteUrl =
${this.context.pageContext.site.absoluteUrl}/SiteAssets/SitePages/${fileresult.data.Name}; console.log('saving....', filePickerResult[0]); onUpdate(field.id, filePickerResult[0]); this.context.propertyPane.refresh(); this.render(); }); } else { console.log('saving....', filePickerResult[0]); onUpdate(field.id, filePickerResult[0]); this.context.propertyPane.refresh(); this.render(); } } }, hideLocalUploadTab: false, hideLocalMultipleUploadTab: true, hideLinkUploadTab: false }) ); }, required: true }, ], disabled: false, }),
Thanks!