React SPFx - Adding files to SharePoint list field using PnPjs - javascript

I'm building an application using SPFx Webpart with React. On one of my components I have a form that the user can fill out. I'm using PnPjs to push the user's responses into my list field. Everything works as expected.
I was looking at how to add a file or attachment field type to a list. I saw I can do it in the powerapps. So now in my "Product" list I have a field called attachments. When I attach files to that field from SharePoint's backend and then make a call to the list using PnPjs the attachment field does not return information about the files. But rather a boolean with a true or false.
pnp.sp.web.lists.getByTitle("Products").items.filter("Id eq '" + this.props.match.params.id + "'").top(1).get().then((items: any[]) => {
console.log(items);
}
So this works perfect and returns back the item which should have had the attachments from the code below. Now in my items console I get back Attachments: true or Attachments: false
I'm using react-dropzone to allow users to upload files. Using PnPjs how do I upload the files to that item?
Here is how I'm creating the item:
pnp.sp.web.lists.getByTitle("Requests").items.add({
Title: this.state.SuggestedVendor,
Client_x0020_Email: this.state.getEmail,
Created: this.state.startDate,
Attachments: //need help here
}
Here is my code for the dropdown files:
onDrop = (acceptedFiles) => {
console.log(acceptedFiles);
//Assuming this is where I do the logic
}
<Dropzone
onDrop={this.onDrop}
multiple
>
{({getRootProps, getInputProps, isDragActive}) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
{isDragActive ? "Drop it like it's hot!" : 'Click me or drag a file to upload!'}
</div>
)}
</Dropzone>
And here is the response I get back from console.log(acceptedFiles);:
[File]
0: File {path: "John-Hancock-signature.png", name: "John-Hancock-signature.png", lastModified: 1590783703732, lastModifiedDate: Fri May 29 2020 13:21:43 GMT-0700 (Pacific Daylight Time), webkitRelativePath: "", …}
length: 1
I found this documentation here on how to push the files : https://pnp.github.io/pnpjs/sp/files/

You have to create the item, and then add an attachment to it.
You can add an attachment to a list item using the add method. This method takes either a string, Blob, or ArrayBuffer. pnp documentation
onDrop = (acceptedFiles) => {
acceptedFiles.forEach(file => {
const reader = new FileReader()
reader.onabort = () => console.log('file reading was aborted')
reader.onerror = () => console.log('file reading has failed')
reader.onload = async () => {
// get file content
const binaryStr = reader.result
// assume we have itemId
let itemId = 1;
let request = await sp.web.lists.getByTitle("Requests").items.getById(itemId)();
await request.attachmentFiles.add("file2.txt", "Here is my content");
}
reader.readAsArrayBuffer(file)
})
}

Related

"In order to select an item please sign in" issue occurring randomly when using Google Drive Picker UI

I'm using Google Drive Picker UI to select a folder and create or update spreadsheet into that folder on a schedule
Sometimes it works as expected but recently it is showing a message called "In order to select an item, please sign in". On clicking "sign in" button it shows "The feature you requested is currently unavailable. Please try again later."
Previously, this used to occur when reauthorizing immediately after revoking access but now I'm requesting with extra params like whom the folder is shared with, created date, folder name to display in front-end. It worked fine for some days but now, the issue above mentioned is occurring frequently.
createPicker(oauthToken, authCode, authUser) {
const googleViewId = window.google.picker.ViewId.FOLDERS;
const docsView = new window.google.picker.DocsView(googleViewId)
.setIncludeFolders(true)
.setMimeTypes('application/vnd.google-apps.folder')
.setSelectFolderEnabled(true);
const picker = new window.google.picker.PickerBuilder()
.addView(docsView)
.setOAuthToken(oauthToken)
.setDeveloperKey(this.props.developerKey)
.setCallback(data => {
if (data.action === window.google.picker.Action.PICKED) {
this.fetchFolderDetails(data, authCode, authUser);
}
});
if (this.props.multiSelect) {
picker.enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED);
}
picker.build().setVisible(true);
}
fetchFolderDetails(data, authCode, authUser) {
window.gapi.client
.init({
apiKey: this.props.developerKey
})
.then(() =>
window.gapi.client.request({
path: 'https://www.googleapis.com/drive/v2/files/' + data.docs[0].id,
params: {
fields: 'permissions, title, createdDate, shared'
}
})
)
.then(response => {
let googleDriveData = {
folderId: data.docs[0].id,
mimeType: data.docs[0].mimeType,
authCode,
authUser,
folderName: response.result.title,
permissions: response.result.permissions,
shared: response.result.shared,
createdTime: response.result.createdDate
};
this.props.onChange(googleDriveData);
});
}
I expect to see the list of folders after authorizing.
Update
Adding a google drive scope somewhat fixed the issue but still the immediate reauthorizing issue persists.

Is there a way to store a file in a React state?

I'm working on a project where I need to store an image in my components state, so that I can send the file and accompanying data to a different component that uploads the data to my Firebase Database and the image to my Firestore.
I've tried doing it by setting the state the normal way, using a functions that is called when an image is submitted. But the state in which I want to pass on the file remains empty.
The initial state:
state = {
title: '',
description: '',
date: '',
ticket: '',
price: '',
image: []
}
The input:
<input type="file" id="image" accept="image/*" onChange={this.handleChangeImage} />
The function that is supposed to handle my problem:
handleChangeImage = (e) => {
const file = e.target.files[0];
this.setState({
[e.target.id]: file
});
}
If I console log the variable "file" I get the file, but console logging state.image will just show me an empty array.
Try changing your handle function to this:
handleChangeImage = (e) => {
const file = e.target.files[0];
const newImages = [...this.state.image];
newImages.push(file);
this.setState({
image: newImages
});
}
This problem is now resolved. I am an idiot. My problem wasn't that the state wasn't updated with the file, the problem was that the console log output happened before the state was updated with the file.
The state was always updated with the file, I just never realised.
The issue was in other words not an issue, just a plain dumb rookie mistake.

Upload JSON file using Angular 6

I'm trying to load a JSON file from the user using this method:
<input
style="display: none"
type="file" (change)="onFileChanged($event)"
#fileInput>
<button (click)="fileInput.click()">Select File</button>
<button (click)="onUpload()">Upload!</button>
and this is the code in the component ts file:
export class MyFileUploadComponent {
selectedFile: File
onFileChanged(event) {
this.selectedFile = event.target.files[0];
console.log(this.selectedFile);
console.log('content: ' + JSON.stringify(this.selectedFile));
}
onUpload() {
// upload code goes here
}
}
the line console.log(this.selectedFile); does provide me with the file meta data which is:
lastModified: 1551625969247
lastModifiedDate: Sun Mar 03 2019 17:12:49 GMT+0200 (Israel Standard Time) {}
name: "manuscripts.json"
size: 6008
type: "application/json"
webkitRelativePath: ""
__proto__: File
But when I'm trying to print it's content using JSON.stringify I get: {} (empty file).
What's the cause?
Thanks.
But when I'm trying to print it's content using JSON.stringify I get: {} (empty file).
This is not a content of JSON file. It's a File object. To read content of JSON you need to use FileReader
onFileChanged(event) {
this.selectedFile = event.target.files[0];
const fileReader = new FileReader();
fileReader.readAsText(this.selectedFile, "UTF-8");
fileReader.onload = () => {
console.log(JSON.parse(fileReader.result));
}
fileReader.onerror = (error) => {
console.log(error);
}
}
JSON.Stringify does not work for File objects in TS/JS. You should extract data from File and then stringify it.
For examlple, extract file content as a string or array of strings using https://developer.mozilla.org/en-US/docs/Web/API/FileReader

Download document on the same page when using antd upload

I am using antd's upload component to allow a user to upload a document. Now after the user has uploaded it, I also want to allow him to download the attachment just uploaded. I know the documentation explains this to do in the following way:
import { Upload, Button, Icon } from 'antd';
const props = {
action: '//jsonplaceholder.typicode.com/posts/',
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
}
},
defaultFileList: [{
uid: '1',
name: 'xxx.png',
status: 'done',
response: 'Server Error 500', // custom error message to show
url: 'http://www.baidu.com/xxx.png',
}],
};
ReactDOM.render(
<Upload {...props}>
<Button>
<Icon type="upload" /> Upload
</Button>
</Upload>,
mountNode
);
But I want to allow the download on the same page and don't want to open a new window on click. How could I do this? Is there a better way to approach this?
The default behavior is to open the file in a new window.
You can override this behavior by providing an onPreview prop for Upload:
onPreview: A callback function, will be executed when file link or preview icon is clicked.
<Upload
//...
onPreview={handlePreview}
>
Now write your own logic to download the file in handlePreview. You can find many examples online on how to achieve that.
const handlePreview = (file) => {
console.log(file);
// write how you want to download
}

ReactJS: How to send multiple files in server (Laravel)

I'm new in ReactJS and my backend is laravel and I have problem regarding inserting multiple files to the database, however if i use only the single upload (inserting one file to the database it's working for me.).
PROBLEM: regarding inserting multiple files in the database.
GOAL: To insert multiple files to the database
I have here
FORMDATA:
const formData = new FormData();
formData.append('myFile', this.state.image);
RESPONSE:
axios.post('/api/save_gallery_album_image', formData).then(response => {
console.log(response);
}).catch(error => (error.response));
OnChange:
handleChangeImage(e){
this.setState({
image:e.target.files[0]
})
// console.log(e.target.files);
}
JSX:
<label>Image</label>
<div className="custom-file">
<input type="file"
name="image"
multiple
onChange={this.handleChangeImage}
className="custom-file-input form-control"
accept="image/x-png,image/gif,image/jpeg"
id="file_selected"/>
<label className="custom-file-label" htmlFor="validatedCustomFile">Choose file...</label>
</div>
Server side Controller:
public function save_gallery_album_image(Request $request)
{
$multiple_gallery_file_upload = $request->file('myFile');
$titleValue = $request->get('titleValue');
$pageValue = $request->get('pageValue');
$now = new DateTime();
if($request->hasFile('myFile'))
{
foreach($multiple_gallery_file_upload as $myfiles)
{
$uniqueid=uniqid();
$original_name=$request->file('myFile')->getClientOriginalName();
$size=$request->file('myFile')->getSize();
$extension=$request->file('myFile')->getClientOriginalExtension();
$name=$uniqueid.'.'.$extension;
$path=$request->file('myFile')->storeAs('public',$name);
DB::insert('INSERT INTO album_category (cid,image,created_at) VALUES (?,?,?) ',[
$titleValue,
$name,
$now
]);
}
return response()->json('Input Saved');
}
}
I am facing the same problem I have fixed like this I hope it's helpful for you.
put this code on the right place and check it's working to upload multiple images Thanks.
<form>
<input name="product_image" type="file" multiple onChange={e => this.HandleProductImage(e)}/>
<button type="submit" onClick={e =>this.submitForm(e)}>
</form>
HandleProductImage = e => {
this.setState({
product_image: e.target.files
});
};
submitForm = e => {
const product = new FormData();
if (this.state.product_image) {
for (const file of this.state.product_image) {
product.append("image", file);
}
}
//then use your API to send form data
}
I guess you should request multiple time as files existed using loop.
When you request array of files in multipart form, the multipart form don't include all of your files. So you should send upload requests separately.
After check the comment, I added some sample code.
OnChange:
handleChangeImage(e) {
// Set file list
let files = e.target.files;
files.map((file) => {
// make diffent formData per each file and request.
let formData = new FormData();
formData.append('myFile', file);
axios.post('/api/save_gallery_album_image', formData)
.then(response => {
console.log(response);
}).catch(error => (error.response));
});
}
If you want to save multiple files in one request, I think you should also change your server-side codes.
For now, your server might save just one file per one request.

Categories