CKEditor Upload Adapter Sends [object Promise] to Server - image

I have been trying to implement CKEditor5 into a vuejs project and after getting all the infrastructure working, I cannot get the actual file to upload to a php server. The code calls the server and if I return a success message and file url, it all works correctly. Here is my code:
<template>
...
<ckeditor :editor="editor" v-model="details.SystemText" :config="editorConfig"></ckeditor>
...
</template>
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
class UploadAdapter {
constructor(loader) {
this.loader = loader;
}
upload() {
return new Promise((resolve, reject) => {
const data = new FormData();
data.append('upload', this.loader.file);
axios({
url: '/index/uploadimage',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;'
},
withCredentials: false
}).then(response => {
if (response.data.result == 'success') {
resolve({
default: response.data.url
});
} else {
reject(response.data.message);
}
}).catch(response => {
reject ( 'Upload failed');
});
});
}
abort() {
}
}
export default {
data () {
details: {},
editor: ClassicEditor,
editorConfig: {
extraPlugins: [ this.MyCustomUploadAdapterPlugin ],
}
},
methods: {
MyCustomUploadAdapterPlugin ( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new UploadAdapter( loader );
};
},
}
Clicking the image icon in the toolbar will show the file select dialogue correctly, and upon file selection will submit a post to the server. However, the binary file is not sent, but simply:
Form Data
------WebKitFormBoundaryqPGA20WRKz9VvADd
Content-Disposition: form-data; name="upload"
[object Promise]
I have spent two days looking at all other plugins like CKFinder and others, and I seem to always get the same content being sent to the server. The line
data.append('upload', this.loader.file);
does not seem to append the actual file which is what I think it should do.
My value of this.loader is
loader.file
Promise {<pending>}
__proto__: Promise
catch: ƒ catch()
constructor: ƒ Promise()
finally: ƒ finally()
then: ƒ then()
Symbol(Symbol.toStringTag): "Promise"
__proto__: Object
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined
Tried using their cloudservice but point to my own urls and that got the upload working.
editorConfig: {
cloudServices: {
tokenUrl: '/index/tokenendpoint',
uploadUrl: '/index/uploadimage'
}
}
I was also able to remove all the upload adapter code.
Thank you

Reason for your problem is that in version 11.0.0 of ckeditor5-upload plugin the API was changed, loader.file is now a Promise (see release notes). Unfortunately the docs were not updated accordingly.
You need to adjust your upload function a little:
upload() {
return this.loader.file
.then( uploadedFile => {
return new Promise( ( resolve, reject ) => {
const data = new FormData();
data.append( 'upload', uploadedFile );
axios( {
url: '/index/uploadimage',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;'
},
withCredentials: false
} ).then( response => {
if ( response.data.result == 'success' ) {
resolve( {
default: response.data.url
} );
} else {
reject( response.data.message );
}
} ).catch( response => {
reject( 'Upload failed' );
} );
} );
} );
}
The docs that had this issue are now fixed and use promise properly. Hope this solves the problem for you!

Use jQuery ajax. I cannot find an equivalent using fetch or axios. The key is setting contentType: false and processData: false.
upload() {
return new Promise((resolve, reject) => {
const data = new FormData();
data.append('postedFile', this.loader.file);
$.ajax({
url: '/index/uploadimage',
data,
contentType: false,
processData: false,
type: 'POST',
success: response => {
resolve({
default: response.data.url
});
},
error: () => {
reject('Upload failed');
}
});
});
}

They are working on it, it is a bug.
https://github.com/ckeditor/ckeditor5/issues/1618

Related

Redux says to use Plain object in action.But dont know where to use plain object

I was searching for my answer in SO, but could not find any suitable one. So here i go with my questions...
In my redux action creator i am fetching API call from isomorphic-unfetch but I am getting the message Error: Actions must be plain objects. Use custom middleware for async actions each time.Though i defined dispatch in my action...
My action code is
const exchangeBuy = ({btc, usdt, id}, url) => {
return (dispatch) => {
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( { btc, usdt, id } )
}).then(
r => r.json()
).then(
r => dispatch({
type: 'EXCHANGE_BUY',
payload: r //here r return an object from mongoDB
})
)
}
}
Also the code that invokes this is
submitExchange(e){
e.preventDefault()
const btc = e.target.btcamount.value
const usdt = e.target.usdprice.value
this.props.exchangeBuy( //Here it is
{
btc: btc,
usdt: usdt,
id: this.props.users.id
},
this.props.apiurl )
}

Image is not validating: "The avatar must be an image"

I'm using a modal to preview an avatar. The even that triggers the modal is outside the ability to have a parent child structure so I have to pass the file object to my UpdateAvatar component.
Html
<avatar-update :image-blob="avatarFile" :show="avatarModalShow"
#close="avatarModalShow = !avatarModalShow"
:change-avatar="updateCrop"> </avatar-update>
Root Instance
data() {
return {
avatarModalShow: false,
avatarFile: null,
}
},
methods: {
onFileChange: function(e) {
this.avatarFile = e.target.files[0];
this.avatarModalShow = !this.avatarModalShow;
},
},
AvatarUpdate
export default {
props: ['show','imgUrl','changeAvatar','imageBlob'],
data() {
return {
image: null,
message: null,
internalImageObj: null
}
},
watch: {
changeAvatar: function(){
this.image = this.imgUrl;
},
imageBlob: function (newVal) {
let reader = new FileReader()
reader.readAsDataURL(newVal)
reader.addEventListener('load', () => {
this.internalImageObj = reader.result
}, false)
}
},
updated: function () {
this.image = this.imgUrl;
},
methods: {
close: function(){
this.$emit('close');
},
submitAvatar: function(){
const avatarFormData = new FormData();
avatarFormData.append('avatar', this.internalImageObj);
console.log(avatarFormData);
axios({
method: 'POST',
url: '/profile/avatar',
data: avatarFormData,
}).then(function (response) {
this.message = "Your avatar has been submitted";
}.bind(this))
.catch(function (error) {
console.log(error);
});
}
}
}
UserController
public function avatar(Request $request)
{
$request->validate([
'avatar' => 'image',
]);
return $request->all();
}
When I return $request->all(); in the avatar function with no validation on the UserController I'm getting this output: avatar:"data:image/png;base64,iVBORw0KGgoAAAANSUhSomeLongString
Error
{message: "The given data was invalid.", errors: {avatar: ["The avatar must be an image."]}}
errors
:
{avatar: ["The avatar must be an image."]}
avatar
:
["The avatar must be an image."]
0
:
"The avatar must be an image."
message
:
"The given data was invalid."
this is because your validation rule for avatar is image or mimes:jpeg,bmp,png this it will look for a file where the mime type is jpeg,bmp,png. but in your case your axios send it as a base64 which don't have a mime type. you need to include 'Content-Type': 'multipart/form-data' header in your axios object like this,
axios({
method: 'POST',
url: '/profile/avatar',
data: avatarFormData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
hope this helps.

File upload with fetch API vuejs returns 419 unknown status

I am using VUE.js with Laravel to upload file using fetch api. I have added the csrf token to the header of the request, but still getting the 419 unknown status. Any help will be appreciated thanks.
Here is the JS of the component
<script>
export default {
name:'UploadModal',
data(){
return {
image:'',
ext:'',
file:''
};
},
methods: {
onFileChange(e) {
var files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.file = files[0];
this.createImage(files[0]);
},
uploadArtwork: function () {
let formData = new FormData();
formData.append('artwork', this.file);
fetch(this.$parent.itemUrl, {
method:'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
'X-CSRF-TOKEN' : Laravel.csrfToken
}
})
.then(res => res.json())
.then(res => {
alert(res);
})
.catch(e => console.log(e));
},
createImage(file) {
var image = new Image();
var reader = new FileReader();
var vm = this;
reader.onload = (e) => {
vm.image = e.target.result;
};
reader.readAsDataURL(file);
},
}
}
</script>
I know this is an old question, but I ran into this issue as well when using fetch and the linked answer (Laravel 5.5 ajax call 419 (unknown status)) did not help, since that relates to jQuery's Ajax method.
For those who are facing the same issue, it looks like this is due to the default credentials setting (defaults to "omit"), which essentially omits the csrf header for some reason. You can get around this by changing credentials to "same-origin" or "include" depending on your needs.
Example:
fetch("/leads", {
method: 'POST',
credentials: "same-origin",
headers: csrf_header
}).then(res => res.json())
.then(
(json) => {
this.setState({
isLoaded: true,
items: json.leads.data,
sort: json.sort,
search: json.search,
sort_by: json.sort_by,
filter: json.filter
});
}
);

uploading profile pic in hapijs 17.0

I am using hapijs version 17.0.1. I am trying to upload an image using ajax request on a hapijs route. Here is my AJAX code to upload profile pic:
var image_file_input = document.getElementById("user_profile_upload");
image_file_input.onchange = function () {
if(this.files != undefined)
{
if(this.files[0] != undefined)
{
var formData = tests.formdata ? new FormData() : null;
if (tests.formdata)
{
//alert(file)
formData.append('image_file', this.files[0]);
formData.append('userId', user_id);
formData.append('memberId', member_id);
}
$.ajax({
url: "/v1/User/uploadUserPic",
data: formData,
type: "POST",
dataType: "json",
contentType: false,
processData: false,
contentType: "multipart/form-data",
success: function(data){
console.log(data);
var errMsg = null;
var resData = null;
if(data.statusCode == 200)
{
resData = data.result;
}
else
{
alert(data.message)
}
},
error: function(error){
alert(error);
}
});
}
}
}
And here is my Hapijs route Code:
var uploadUserPic = {
method: 'POST',
path: '/v1/Module/uploadUserPic',
config: {
description: 'Update Image For User',
tags: ['api', 'User'],
auth: 'session',
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data'
},
validate: {
payload: {
userId : Joi.string().regex(/^[a-f\d]{24}$/i).required(),
memberId: Joi.string().required(),
image_file: Joi.object().required(),
},
failAction: FailCallBack
}
},
handler: function (request, reply) {
var resultData = null;
var error = null;
return new Promise(function (resolve) {
var multiparty = require('multiparty');
var fs = require('fs');
var form = new multiparty.Form();
form.parse(request.payload, function (err, fields, files) {
if(err)
{
error = err;
resolve();
}
else
{
var mkdirp = require('mkdirp');
var img_dir = "./files/users/";
mkdirp(img_dir, function (err) {
if (err)
{
error = err;
console.error(err);
resolve();
}
else
{
var oldpath = files.image_file.path;
var newpath = "./files/users/"+requestPayload.userId+".png";
fs.rename(oldpath, newpath, function (err) {
if(err)
{
error = err;
}
resolve();
});
}
});
}
});
}).then(function (err, result) {
if(err) return sendError(err);
if(error) return sendError(error)
return {
"statusCode": 200,
"success": true
};
});
}
}
The above code gives me following error cannot read property 'content-length' of undefined on line form.parse(request.payload, function (err, fields, files) {});
Please let me know If I am doing something wrong. If I replace the url in ajax request with anohter url that I have written in php then it works perfectly. which means that something is wrong with my hapijs/nodejs code.
There's a good post on how to handle file uploads in Hapi.js (written in version 16) https://scotch.io/bar-talk/handling-file-uploads-with-hapi-js
Since you are using payload.parse = true, I am not seeing a particular reason why you have to use multiparty. I have the following working code that would save files (of any type) uploaded from client into uploads directory on the server (Please do not use directly on production as no sanitation is done)
{
path: '/upload',
method: 'POST',
config: {
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data'
},
validate: {
payload: {
files: Joi.array().single()
}
}
},
handler: function(request) {
const p = request.payload, files = p.files
if(files) {
console.log(`${files.length} files`)
files.forEach(async file => {
const filename= file.hapi.filename
console.log(`Saving ${filename} to ./uploads`)
const out = fs.createWriteStream(`./uploads/${filename}`)
await file.pipe(out)
})
}
return {result: 'ok'}
}
}
You can use the following curl command to test
curl http://localhost:8080/upload -F 'files=#/path/to/a/note.txt' -F 'files=#/path/to/test.png' -vvv
There are a few issues with your code. First in your $.ajax call, you have specified contentType twice, although it's not a syntax error but it's careless to code like that. Second the function's signature inside your .then() block is incorrect. You are mixing the idea of Promise and callback. I don't think the following line will be triggered
if(err) return sendError(err);
One last trivial thing, you said you are using Hapi 17 but based on the handler function's signature
handler: function (request, reply) {
...
Seems you are not totally onboard with Hapi17 as the new signature is
handler: function (request, h) {
And it's not just the rename of reply to h.

Does Vue.JS work with AJAX http calls?

I am trying to do the following from my HTML:
var vm = new Vue({
el: '#loginContent',
data: {
main_message: 'Login',
isLoggedIn: false,
loginError: '',
loginButton:'Login'
},
methods: {
onLogin: function() {
//this.$set(loginSubmit, 'Logging In...');
var data = {
email: $('#email').val(),
password: $('#password').val(),
};
$.ajax({
url: '/api/login',
data: data,
method: 'POST'
}).then(function (response) {
if(response.error) {
console.err("There was an error " + response.error);
this.loginError = 'Error';
} else {
//$('#loginBlock').attr("hidden",true);
console.log(response.user);
if(response.user) {
this.isLoggedIn = true;
} else {
this.loginError = 'User not found';
}
}
}).catch(function (err) {
console.error(err);
});
}
}
});
Basically user presses the login button, onLogin method is called that sends a post to my API. The post is working fine and I do get the response back in the .then() promise.
But, trying to do things like this.isLoggedIn = true; does not update my DOM with what I am expecting the HTML to do when the user logs in.
Could be that I am in some sort of background thread (sorry, mobile developer here) when I get the response in the promise and it can't find the "vm" instance?
Thanks
It is probably happening because your this is not pointing to correct scope, scope of this changes inside an $.ajax call, so you just have to do something like following:
methods: {
onLogin: function() {
//this.$set(loginSubmit, 'Logging In...');
var data = {
email: $('#email').val(),
password: $('#password').val(),
};
var that = this
$.ajax({
url: '/api/login',
data: data,
method: 'POST'
}).then(function (response) {
if(response.error) {
console.err("There was an error " + response.error);
that.loginError = 'Error';
} else {
//$('#loginBlock').attr("hidden",true);
console.log(response.user);
if(response.user) {
that.isLoggedIn = true;
} else {
that.loginError = 'User not found';
}
}
}).catch(function (err) {
console.error(err);
});
}
}
I would propose another method use ES6 Arrow Functions like '=>'. It is simple and do not need extra variable.Like following:
$.ajax({
url: '/api/login',
data: data,
method: 'POST'
}).then((response) => {
if(response.error) {
console.err("There was an error " + response.error);
this.loginError = 'Error';
} else {
//$('#loginBlock').attr("hidden",true);
console.log(response.user);
if(response.user) {
this.isLoggedIn = true;
} else {
this.loginError = 'User not found';
}
}
}).catch(function (err) {
console.error(err);
});
You might want to take a look at axios. I used $.ajax and got it working, but found axios and prefer axios over the ajax library.

Resources