I'm using Vue with Laravel and I tried to do a simple crud which is working good regarding things like title or description of an article (text-fields) but when I try to send an image file, it doesn't work. I have tried formData but to no avail.
This is my form in template, title goes in the database with no problem, if I console log selectedFile then it shows the file selected correctly but the addArticle method not attaching the images
<form #submit.prevent="addArticle" class="container">
<label>Title</label>
<input type="text" v-model="article.title" />
<label>Image</label>
<input type="file" v-on:change="selectedFile" />
<button type="submit">Create</button>
</form>
This is my script
<script>
export default {
data() {
return {
fileSelected: null,
article: {},
};
},
methods: {
addArticle() {
var formData =new FormData();
formData.append(this.article.image, this.fileSelected);
axios
.post("http://localhost:8000/api/articles", this.article)
.then((response) => this.$router.push({ name: "ArticlesList" }))
.catch((err) => console.log(err))
.finally(() => (this.loading = false));
}
,
selectedFile(event) {
this.fileSelected = event.target.files[0];
},
},
};
</script>
this is my code
<input type="file" #change="onFileChange">
onFileChange(e) {
this.sendphoto = e.target.files[0];
},
editUser() {
var self = this;
let formData = new FormData();
formData.append("image" , self.sendphoto )
let config = {
header : {
'Content-Type' : 'multipart/form-data'
}
}
axios.post('/edit-user' , formData , config)
.then(function (response) {
})
.catch(function (error) {
console.log(error);
})
},
You are creating a FormData object but you are not sending it within your Axios request.
In order to send the file and form data, you have to append everything to FormData object.
<script>
export default {
data() {
return {
fileSelected: null,
article: {},
};
},
methods: {
addArticle() {
var formData =new FormData();
formData.append('image', this.fileSelected);
formData.append('title', this.article.title);
axios
.post("http://localhost:8000/api/articles", formData)
.then((response) => this.$router.push({ name: "ArticlesList" }))
.catch((err) => console.log(err))
.finally(() => (this.loading = false));
}
,
selectedFile(event) {
this.fileSelected = event.target.files[0];
},
},
};
</script>
this worked in sending image files as string on database, hopefully it helps other people that are having similar problems
setup() {
const base64 = ref()
const changeFile= async(event) => {
const file = event.target.files[0];
base64.value = await convertBase64(file);
}
const convertBase64 = (file) => {
return new Promise ((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = () => {
resolve(fileReader.result)
}
fileReader.onerror = (error) => {
reject(error)
}
})
}
const form = reactive({
title: " ",
body: " ",
image: base64,
})
const submitForm = () => {
axios.post("http://localhost:8000/api/articles", form)
}
return { changeFile, submitForm, form}
},
Related
I am trying to implement custom upload adapter but it is not working.
On uploading image my upload function is not running(upload function is created inside uploadAdapter function) and no request is getting sent to backend. Please see what is the problem in code.
const API_URl = "https://noteyard-backend.herokuapp.com";
const UPLOAD_ENDPOINT = "api/blogs/uploadImg";
function uploadAdapter(loader) {
return {
upload: () => {
return new Promise((resolve, reject) => {
const body = new FormData();
loader.file.then(file => {
body.append("uploadImg", file);
//AuthorizedApi is simple axios request.
AuthorizedApi.post(`${API_URl}/${UPLOAD_ENDPOINT}`, {
body,
})
.then(res => res.json())
.then(res => {
resolve({ default: `${API_URl}/${res.url}` });
})
.catch(err => {
reject(err);
});
});
});
},
};
}
function uploadPlugin(editor) {
editor.plugins._plugins.get("FileRepository").createUploadAdapter = loader => {
return uploadAdapter(loader);
};
}
//This is my CKEditor code snippet
<CKEditor
editor={TextEditor}
config={{
...EDITOR_CONFIG[theme],
extraPlugins: [uploadPlugin],
link: {
defaultProtocol: "https://",
},
}}
data={stripTrackingImage(value) ?? ""}
onReady={editor => {
//add toolbar
window.editor = editor;
if (disabled) {
editor.isReadOnly = true;
} else {
document
.querySelector(`#${toolbarId}`)
?.appendChild(editor.ui.view.toolbar.element);
editor.on("handle_attachment_click", addAttachment);
//adjust tooltip position
editor.ui.view.toolbar.items.map(item => {
//button positionging( without dropdowns)
item.tooltipPosition = "se";
//for dropdowns
if (item.buttonView) {
item.buttonView.tooltipPosition = "se";
}
return item;
});
}
console.log(editor, "editor");
}}
onChange={(event, editor) => setValue(editor.getData())}
isReadOnly={disabled}
/>
On uploading image , no request is getting sent to given URL, please try to find the problem
I am trying to upload multiple files with laravel and vue 3. I have 3 attributes user_files, book_files[]
<input
type="file"
class="form-control"
name="user_files[]"
#change="onFileChange"
multiple
id="user_files"
placeholder="User details"
/>
</div>
const form = reactive({
first_name: "",",
hourly_rate: 0,
user_files: [],
book_files: []
user_name: "",
course_date: "",
});
const onFileChange = (e) => {
for (let i = 0; i < e.target.files.length; i++) {
form.user_files.push(e.target.files[i]);
}
};
const { errors, storeUser } = useUsers();
const saveUser = async () => {
await storeUser({ ...form });
};
return {
form,
saveUser,
onFileChange,
};
How can I rewrite the function onFileChange for multiple attributes, right now it is only for `user_files.
const storeUser = async (data, medical_file) => {
let formData = new FormData();
(data.user_files ?? []).forEach((file, key) => {
formData.append("user_files[]", file);
});;
errors.value = "";
try {
const status = await http.post("/api/users", formData, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
Also in above js function, I need to append rest of the data like first_name, user_name and others into the formData as well, and loop over the other files along with user_files. How should I do that (data.medical_file ?? []).forEach((file, key) => { formData.append("medical_file[]", file); });; in this function.
I have a problem with saving pictures in the database. I want to do a post Method, where i can safe a file in a directory and save the picture link in the database.
Here is my Code:
`const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
reader.onerror = error => reject(error)
})`
` const [base64Image, setBase64Image] = useState("")
const [imagePath, setImagePath] = useState("")
const fileInput = useRef(null)`
`const onFileInputChange = async (e) => {
const file = fileInput.current.files[0]
if (!file) return
const base64 = await toBase64(file)
setBase64Image(base64)}`
` const handleSubmitImage = async (e) => {
e.preventDefault()
if (!base64Image) return
const response = await fetch("/public", {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(base64Image)
})
const data = await response.json()
setImagePath(data.filePath)
}`
Post:
`const handleSubmit = async (e) => {
e.preventDefault()
setIsLoading(true)
setErrors(defaultModel)
const result = validateModel(post)
if (!result.isValid) {
setErrors(result.errors)
setIsLoading(false)
return
}
if (post.id) {
await updatePost(post, session.accessToken)
alert("Post updated!")
router.push(`/posts/${post.id}`)
} else {
const newPost = await createPost(post, session.accessToken)
alert("Post created!")
router.push(`/posts/${newPost.id}`)
}
setIsLoading(false)
}
`
` <fieldset onSubmit={handleSubmitImage} className={styles.form}>
<p>Image:</p>
<input value={post.image}
type="file"
accept=".png,.jpg"
ref={fileInput}
onChange={onFileInputChange}
/>
{/* eslint-disable-next-line #next/next/no-img-element */}
{base64Image && <img src={base64Image} style={{width: "1000px", height: "1000px"}} alt={""}/>}
{imagePath && <p>
<Link href={`http://localhost:3000${imagePath}`}
passHref><a>http://localhost:3000{imagePath}</a></Link>
</p>
}
</fieldset>`
Right now i can connect to the Explorer and pick an Image. I can also display the image. If i press on create, it doesnt work properly with saving the image in the database.
I am sending a file to an api endpoint via react webapp and for now as a mock up have been using file upload. I have since added camera capabilities as I plan for users to be able to take a picture and upload that picture. I'm using the react-html5-camera-photo which captures a datauri element. How do I convert this to a file to upload to the endpoint? Or am I approaching this incorrectly?
File Upload:
handleUploadImage=()=> {
console.log(this.state.selectedFile)
const data = new FormData();
data.append('file', this.state.selectedFile);
data.append('filename', this.state.selectedFile.name);
fetch('http://localhost:5000/upload', {
method: 'POST',
body: data,
})
.then((response) => {
response.json()
.then((response) => {
//this.setState({ imageURL: `http://localhost:5000/${body.file}` });
console.log(response.text)
this.setState({ text: response.text})
this.getPlate()
})
});
}
Camera Code:
startCamera = () => {
console.log('starting camera')
if(!this.state.cameraLoad) {
this.setState({ cameraLoad: true, camIconData: collapse}) ;
} else {
this.setState({ cameraLoad: false, camIconData: expand}) ;
}
}
onTakePhoto = (dataUri) => {
// Do stuff with the dataUri photo...
console.log('photo taken');
this.setState({ camColor: 'green', dataUri : dataUri })
}
// will upload photo after user confirms to upload
uploadPhoto = e => {
console.log('uploading photo')
console.log(this.state.dataUri)
this.setState({ camColor: 'green', dataUri: null, cameraLoad: false });
};
The dataUri element looks something like:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAALQCAYAAADPfd1WAAAgAElEQVR4XlS9h5Nk63HdmeVdV5vpmXnzDAEQBAiKwlJmIzZiQ/u/72qDWongkpREQFjRgHh2/LQp7zd+52TeavSLfjPTXXXr3s/kl3n
Here is the complete example. Please write a CameraComponent as below where you will find submitPhoto() function and this function is responsible for uploading image to the api.
import React, {useState} from 'react';
import Camera from 'react-html5-camera-photo';
import 'react-html5-camera-photo/build/css/index.css';
import ImagePreview from "./ImagePreview";
import axios from 'axios';
function CameraComponent(props) {
const [dataUri, setDataUri] = useState('');
const isFullscreen = false;
const cameraRef = React.useRef();
function handleTakePhoto(dataUri) {
console.log('takePhoto');
}
function handleTakePhotoAnimationDone(dataUri) {
console.log(dataUri, 'takePhoto');
setDataUri(dataUri);
}
function handleCameraError(error) {
console.log('handleCameraError', error);
}
function handleCameraStart(stream) {
console.log('handleCameraStart');
}
function handleCameraStop() {
console.log('handleCameraStop');
}
function submitPhoto() {
const url = "http://localhost:3000/api/img";
const request = axios.post(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
img: dataUri
})
}).catch(error => {
console.warn(error);
});
}
return (
<div>
<button onClick={submitPhoto}>Upload</button>
{(dataUri)
? <ImagePreview dataUri={dataUri}
isFullscreen={isFullscreen}
/> :
<Camera ref={cameraRef}
onTakePhoto={(dataUri) => {
handleTakePhoto(dataUri);
}}
onTakePhotoAnimationDone={(dataUri) => {
handleTakePhotoAnimationDone(dataUri);
}}
onCameraError={(error) => {
handleCameraError(error);
}}
onCameraStart={(stream) => {
handleCameraStart(stream);
}}
onCameraStop={() => {
handleCameraStop();
}}
/>
}
</div>
);
}
export default CameraComponent;
Here is the another component ImagePreview which will preview the image
import React from 'react';
import PropTypes from 'prop-types';
import './styles/imagePreview.css';
export const ImagePreview = ({ dataUri, isFullscreen }) => {
let classNameFullscreen = isFullscreen ? 'demo-image-preview-fullscreen' : '';
return (
<div className={'demo-image-preview ' + classNameFullscreen}>
<img src={dataUri} />
</div>
);
};
ImagePreview.propTypes = {
dataUri: PropTypes.string,
isFullscreen: PropTypes.bool
};
export default ImagePreview;
use this :
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
var convertedFile = dataURLtoFile(dataUri, `${Math.random(10)}.jpg`);
I have a complete mess of a component. Right now I pass a function I have been trying a million things I can not make it work.
export default class DatafileUpload extends Component {
initialState = {
fileUploading: false,
fileList: [],
status: 'empty', // 'empty' | 'active' | 'success' | 'exception'
file: {}
}
state = this.initialState
static propTypes = {
userId: PropTypes.string.isRequired,
datasetId: PropTypes.string.isRequired
}
scrubFilename = (filename) => filename.replace(/[^\w\d_\-.]+/ig, '')
requestSignedS3Url = (file) => {
const filename = this.scrubFilename(file.name)
const params = {
userId: this.props.userId,
contentType: file.type,
Key: `${filename}`
};
return api.get('/s3/signUpload', { params })
.then(response => {
return response.data;
})
.catch(error => {
console.error(error);
});
}
uploadFile = (file) => {
this.requestSignedS3Url(file)
.then(signResult => this.uploadToS3(file, signResult))
.catch(error => console.log(error))
}
createCORSRequest = (method, url, opts) => {
opts = opts || {};
let xhr = new XMLHttpRequest();
if (xhr.withCredentials != null) {
xhr.open(method, url, true);
if (opts.withCredentials != null) {
xhr.withCredentials = opts.withCredentials;
}
} else if (typeof XDomainRequest !== "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
};
stepFunctions = () => {
return {
preprocess: (file) => {
console.log('Pre-process: ' + file.name);
},
onProgress: (percent, message, file) => {
this.setState({ fileUploading: true })
console.log('Upload progress: ' + percent + '% ' + message);
},
onFinish: (signResult) => {
this.setState({ fileUploading: false })
console.log("Upload finished: " + signResult.publicUrl)
},
onError: (message) => {
this.setState({ fileUploading: false })
console.log("Upload error: " + message);
},
scrubFilename: (filename) => {
return filename.replace(/[^\w\d_\-\.]+/ig, '');
},
onFinishS3Put: (signResult, file) => {
console.log(signResult)
return console.log('base.onFinishS3Put()', signResult.publicUrl);
}
}
}
uploadToS3 = async (file, signResult) => {
const xhr = await this.createCORSRequest('PUT', signResult.signedUrl);
const functions = this.stepFunctions()
functions.preprocess(file)
if (!xhr) {
functions.onError('CORS not supported', file);
} else {
xhr.onload = () => {
if (xhr.status === 200) {
functions.onProgress(100, 'Upload completed', file);
return functions.onFinishS3Put('potatopotato', file);
} else {
return functions.onError('Upload error: ' + xhr.status, file);
}
};
xhr.onerror = () => {
return functions.onError('XHR error', file);
};
xhr.upload.onprogress = (e) => {
let percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return functions.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
}
};
}
xhr.setRequestHeader('Content-Type', file.type);
if (signResult.headers) {
const signResultHeaders = signResult.headers
Object.keys(signResultHeaders).forEach(key => {
const val = signResultHeaders[key];
xhr.setRequestHeader(key, val);
})
}
xhr.setRequestHeader('x-amz-acl', 'public-read');
this.httprequest = xhr;
return xhr.send(file);
};
handleChange = ({ file, fileList }) => {
const functions = this.stepFunctions()
functions.preprocess(file)
if (!file) {
functions.onError('CORS not supported', file);
} else {
file.onload = () => {
if (file.status === 200) {
functions.onProgress(100, 'Upload completed', file);
return functions.onFinishS3Put('potatopotato', file);
} else {
return functions.onError('Upload error: ' + file.status, file);
}
};
file.onerror = () => {
return functions.onError('XHR error', file);
};
file.upload.onprogress = (e) => {
let percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return functions.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
}
};
}
console.log('File: ', file)
// always setState
this.setState({ fileList });
}
render() {
const props = {
onChange: this.handleChange,
multiple: true,
name: "uploadFile",
defaultFileList: this.initialState.fileList,
data: this.uploadFile,
listType: "text",
customRequest: ????,
showUploadList: {
showPreviewIcon: true,
showRemoveIcon: true
},
onProgress: ( {percent} ) => {
this.setState({ fileUploading: true })
console.log('Upload progress: ' + percent + '% ' );
},
onError: (error, body) => {
this.setState({ fileUploading: false })
console.log("Upload error: " + error);
},
onSuccess: (body)=> {
console.log(body)
return console.log('base.onFinishS3Put()');
}
};
return (
<Upload {...props} fileList={this.state.fileList}>
<Button>
<Icon type="upload" /> Upload
</Button>
</Upload>
)
}
}
I know this code is a mess that doesn't make sense and have duplicated data all around. I want it to make it work and then clean up/optimse. Basically I am not able to make the component progress bar update nor with the onChange nor when I am trying to use the customRequest. When is customRequest called? This is not very abundant in explanations... I don't understand how does it do the replacement of Ajax upload.
I was struggling with that as well and then I found your question.
So the way I found to use customRequest and onChange is:
<Upload name="file" customRequest={this.customRequest} onChange={this.onChange}>
<Button>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
...
onChange = (info) => {
const reader = new FileReader();
reader.onloadend = (obj) => {
this.imageDataAsURL = obj.srcElement.result;
};
reader.readAsDataURL(info.file.originFileObj);
...
};
...
customRequest = ({ onSuccess, onError, file }) => {
const checkInfo = () => {
setTimeout(() => {
if (!this.imageDataAsURL) {
checkInfo();
} else {
this.uploadFile(file)
.then(() => {
onSuccess(null, file);
})
.catch(() => {
// call onError();
});
}
}, 100);
};
checkInfo();
};
There are probably better ways to do it, but I hope that helps you.
I struggled it a lot and find an efficient way to handle this case.
first- you should mess with the customRequest only when you need to change to body and the request type (like using post instead of 'put' or using xml or add another extra header).
for the signing Url you can send in the action prop callback which return a promise with the right Url to upload like:
handleUplaod = (file: any) => {
return new Promise(async (resolve, reject) => {
const fileName = `nameThatIwant.type`;
const url = await S3Fetcher.getPresignedUrl(fileName);
resolve(url);
});
and render like:
render(){
return(
....
<Upload
action={this.handleUplaod}
....
Upload>
the uploader take the url from the action prop.
the onChange method which is provided also will be called any time the status of upload is changed-
onChange# The function will be called when uploading is in progress,
completed or failed.
When uploading state change, it returns:
{ file: { /* ... / }, fileList: [ / ... / ], event: { / ...
*/ }, }
when upload started
you will need to activate the file reader from that.
like:
....
fileReader = new FileReader();
.....
onChange = (info) => {
if (!this.fileReader.onloadend) {
this.fileReader.onloadend = (obj) => {
this.setState({
image: obj.srcElement.result, //will be used for knowing load is finished.
});
};
// can be any other read function ( any reading function from
// previously created instance can be used )
this.fileReader.readAsArrayBuffer(info.file.originFileObj);
}
};
notice when completed stage that event=undefind
To update the UI from the upload events you should use the options variables from customRequest and call them whenever you need.
onSuccess- should be called when you finish uploading and it will change the loading icon to the file name.
onError- will paint the file name filed to red.
onProgress- will update the progress bar and should be called with {percent: [NUMBER]} for updating.
for example in my code-
customRequest = async option => {
const { onSuccess, onError, file, action, onProgress } = option;
const url = action;
await new Promise(resolve => this.waitUntilImageLoaded(resolve)); //in the next section
const { image } = this.state; // from onChange function above
const type = 'image/png';
axios
.put(url, Image, {
onUploadProgress: e => {
onProgress({ percent: (e.loaded / e.total) * 100 });
},
headers: {
'Content-Type': type,
},
})
.then(respones => {
/*......*/
onSuccess(respones.body);
})
.catch(err => {
/*......*/
onError(err);
});
};
waitUntilImageLoaded = resolve => {
setTimeout(() => {
this.state.image
? resolve() // from onChange method
: this.waitUntilImageLoaded(resolve);
}, 10);
};
I used axios but you can use other libraries as well
and the most important part-
render(){
return(
....
<Upload
onChange={this.onChange}
customRequest={this.customRequest}
...>