Angular 2 Multipart AJAX Upload - ajax

I'm using Angular 2 with Spring MVC. I currently have an Upload component that makes an AJAX call to the Spring backend and returns a response of parsed data from a .csv file.
export class UploadComponent {
uploadFile: function(){
var resp = this;
var data = $('input[type="file"]')[0].files[0];
this.fileupl = data;
var fd = new FormData();
fd.append("file", data);
$.ajax({
url: "uploadFile",
type: "POST",
data: fd,
processData: false,
contentType: false,
success: function(response) {
resp.response = response;
},
error: function(jqXHR, textStatus, errorMessage) {
console.log(errorMessage);
}
});
};
}
This works, I get a valid response back; however, is there a more angular 2 way to pass this file to Spring and receive a response? I've been looking into creating an injectible service and using subscribe, but I've been struggling to get a response back

I ended up doing the following:
import { Component, Injectable } from '#angular/core';
import { Observable} from 'rxjs/Rx';
const URL = 'myuploadURL';
#Component({
selector: 'upload',
templateUrl: 'upload.component.html',
styleUrls: ['upload.component.css']
})
export class UploadComponent {
filetoUpload: Array<File>;
response: {};
constructor() {
this.filetoUpload = [];
}
upload() {
this.makeFileRequest(URL, [], this.filetoUpload).then((result) => {
this.response = result;
}, (error) => {
console.error(error);
});
}
fileChangeEvent(fileInput: any){
this.filetoUpload = <Array<File>> fileInput.target.files;
}
makeFileRequest(url: string, params: Array<string>, files: Array<File>) {
return new Promise((resolve, reject) => {
let formData: any = new FormData();
let xhr = new XMLHttpRequest();
for(let i =0; i < files.length; i++) {
formData.append("file", files[i], files[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else {
reject(xhr.response);
}
}
};
xhr.open("POST", url, true);
xhr.send(formData);
});
}
}
I can then inject a response into my html like:
<div class="input-group">
<input type="file" id="file" name="file" placeholder="select file" (change)="fileChangeEvent($event)">
<input type="submit" value="upload" (click)="upload()" class="btn btn-primary">
</div>
<div *ngIf="response">
<div class="alert alert-success" role="alert">
<strong>{{response.myResponseObjectProperty | number}}</strong> returned successfully!
</div>
This has support for multiple file uploads. I created it as an injectable service in this plunkr:
https://plnkr.co/edit/wkydlC0dhDXxDuzyiDO3

Related

Redirect route and display message

I am wondering if there is a way to redirect a route or return a Response with a data and fetch it at another page with the loader function.
Basically I am trying to create a new object with a form and redirect to another page where I wanted to display a creation success message.
Here is a form page example:
I am trying to send the message in the Response body.
import { ActionFunction, Form } from "remix";
export const action: ActionFunction = async ({ request }) => {
// const formData = await request.formData();
return new Response(JSON.stringify({ message: "Hello world!" }), {
status: 303,
headers: {
Location: "/new-page",
},
});
};
export default function Index() {
return (
<div>
<Form method="post">
<input type="text" id="name" name="name" />
<button type="submit">Submit</button>
</Form>
</div>
);
}
And at the NewPage I needed to know if there is a way to get the message on the redirect response.
import { ActionFunction } from "remix";
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
// Get message here
return {
message: "",
};
};
export default function NewPage() {
return <div>New Page</div>;
}
It's a good use case for session flash message 😎
https://remix.run/docs/en/v1/api/remix#sessionflashkey-value
The documentation provides a good example, but the idea behind that is :
Get your form data in Index's action
Store the stringify data in a session cookie flash message
Return a response, using redirect function (helper imported from remix, that make a Response redirect for you)
In NewPage's loader, read the session cookie message and return it. Don't forget to commit your session, it'll delete this flash message for you
Use useLoaderData hook in your component to get the loader's return data
//sessions.server.ts
import { createCookieSessionStorage } from "remix";
// https://remix.run/docs/en/v1/api/remix#createcookiesessionstorage
const { getSession, commitSession, destroySession } =
createCookieSessionStorage({
cookie: {
name: "__session",
secrets: ["r3m1xr0ck5"], // should be a process.env.MY_SECRET
sameSite: "lax",
},
});
import { ActionFunction, Form } from "remix";
import { getSession, commitSession } from "./sessions";
export const action: ActionFunction = async ({ request }) => {
// const formData = await request.formData();
// Get session
const session = await getSession(
request.headers.get("Cookie")
);
session.flash("myMessageKey", "Hello world!");
return redirect("/new-page", {
headers: {
"Set-Cookie": await commitSession(session),
},
});
};
export default function Index() {
return (
<div>
<Form method="post">
<input type="text" id="name" name="name" />
<button type="submit">Submit</button>
</Form>
</div>
);
}
import { LoaderFunction } from "remix";
import { getSession, commitSession } from "./sessions";
export const loader: LoaderFunction = async ({ request }) => {
const formData = await request.formData();
// Get message here
const session = await getSession(
request.headers.get("Cookie")
);
const message = session.get("myMessageKey") || null;
return json(
{ message },
{
headers: {
"Set-Cookie": await commitSession(session), //will remove the flash message for you
// "Set-Cookie": await commitSession(session, { maxAge: SESSION_MAX_AGE }), //re set max age if you previously set a max age for your sessions.
},
}
);
};
export default function NewPage() {
const { message } = useLoaderData();
return <div>New Page {message}</div>;
}

Asp.net core - How to implement realtime using signalR

I am adding a comments part in my project, I would like to use real-time using signalR.
I'm using Ajax for adding comments, and I want to refresh data for all users after the comment inserts into the database.
This is my code in Razor view :
<form asp-action="SendComment" asp-controller="Home" asp-route-subId="#Model.Subject.Id"
asp-route-AccountName="#User.Identity.Name" onsubmit="return jQueryAjaxPost(this);">
<textarea name="comment" id="myTextbox" required class="form-control mb-3" rows="3" cols="1" placeholder="اكتب هنا"></textarea>
<div class="d-flex align-items-center">
<button type="submit" id="myBtn" class="btn bg-blue-400 btn-labeled btn-labeled-right ml-auto"><b><i class="icon-paperplane"></i></b> ارسال</button>
</div>
</form>
Ajax code :
jQueryAjaxPost = form => {
try {
$.ajax({
type: 'POST',
url: form.action,
data: new FormData(form),
contentType: false,
processData: false,
success: function (res) {
if (res.isValid) {
$('#view-all').html(res.html)
}
else
$('#form-modal .modal-body').html(res.html);
},
error: function (err) {
console.log(err)
}
})
//to prevent default form submit event
return false;
} catch (ex) {
console.log(ex)
}
}
signalR code (Not finished)
<reference path="../lib/signalr/browser/signalr.js" />
$(() => {
let connection = new signalR.HubConnectionBuilder().withUrl("/signalServer").build();
connection.start();
connection.on("refreshData", function () {
loadData();
});
loadData();
function loadData() {
debugger;
$.ajax({
type: 'GET',
url: '#Url.Action("refreshComments","Home")',
success: function (res) {
$('#view-all').html(res);
}
})
}
});
Code-behind :
var newComment = new CourseComment
{
Comment = comment,
Date = DateTime.Now,
ApplicationUser = user,
SubjectId = subId,
CreatedDate = DateTime.Now
};
_courseCommnt.Entity.Insert(newComment);
await _courseCommnt.SaveAsync();
await _signalR.Clients.All.SendAsync("refreshData");
_toastNotification.AddSuccessToastMessage("تم ارسال التعليق بنجاح");
var courseComments = await _courseCommnt.Entity.GetAll().Include(a => a.ApplicationUser)
.Where(a => a.SubjectId == subId).OrderByDescending(a => a.Date).AsNoTracking().ToListAsync();
var vm = new HomeViewModel
{
CourseComments = courseComments
};
return Json(new
{
isValid = true,
html = Helper.RenderRazorViewToString(this, "_SubjectComments", vm)
});

Upload multiple file with VueJs and Laravel

I try to do a single post request to upload multiple files. Now I have a functionally method that works for multiple files. But I want a single request.
submitFile(){
this.contract_file.forEach((file) =>{
let formData = new FormData();
formData.append('file', file.file);
axios.post('contracts/uploadfile/' + this.id_contract,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
}
}
).then(function(){
//
})
.catch(function(){
//
});
})
},
public function uploadFile(Request $request, Contract $contract)
{
$filename = $request->file('file')->getClientOriginalName();
$path = $request->file('file')->store($contract->id,'uploads');
$contractFile = new ContractFile();
$contractFile->fill([
'contract_id' => $contract->id,
'name' => $filename,
'path' => $path,
])->save();
}
Update:
This is what I changed,but..
let formData = []
this.contract_file.forEach((file,index) =>{
formData[index] = new FormData();
formData[index].append('file', file.file);
})
foreach($request->file('file') as $file){
//same code but I use $fille
}
Message:
Missing boundary in multipart/form-data POST data in Unknown
Update2:
<file-upload
class="btn btn-primary"
:multiple="true"
:drop="true"
:drop-directory="true"
v-model="files"
#input-filter="inputFilter"
#input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select files
</file-upload>
My answer is not properly tested since I had to adapt my code. Let me know if it doesn't work or if I'm missing something.
Basically, I built my own FormData to be more flexible and easier to reuse.
Form.vue
<template>
<div>
<input #change="upload($event)"
type="file"
name="picture"
id="new-file"
class="custom-file-input"
aria-label="picture"
multiple
>
<label class="custom-file-label" for="new-file">
<span>File...</span>
<span class="btn-primary">Browse</span>
</label>
<button #click="submit" type="button" >Submit</button>
</div>
<template>
<script>
import MyFormData from "./MyFormData";
export default {
data() {
return {
form: new MyFormData({contract_id: 5, files: []})
}
},
methods: {
upload(event) {
for (let file of event.target.files) {
try {
let reader = new FileReader();
reader.readAsDataURL(file); // Not sure if this will work in this context.
this.form.files.push(file);
} catch {}
}
},
submit(){
this.form.post('/my-url')
.catch(errors => {
throw errors;
})
.then((response) => location = response.data.redirect);
}
}
}
</script>
MyFormData.js
export default class MyFormData {
constructor(data, headers) {
// Assign the keys with the current object MyFormData so we can access directly to the data:
// (new FormData({myvalue: "hello"})).myvalue; // returns 'hello'
Object.assign(this, data);
// Preserve the originalData to know which keys we have and/or reset the form
this.originalData = JSON.parse(JSON.stringify(data));
this.form = null;
this.errors = {};
this.submitted = false;
this.headers = headers || {}
}
// https://stackoverflow.com/a/42483509/8068675
// It will build a multi-dimensional Formdata
buildFormData(data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File) && !(data instanceof Blob)) {
Object.keys(data).forEach(key => {
this.buildFormData(data[key], parentKey ? `${parentKey}[${key}]` : key);
});
} else {
const value = data == null ? '' : data;
this.form.append(parentKey, value);
}
}
// Returns all the new / modified data from MyFormData
data() {
return Object.keys(this.originalData).reduce((data, attribute) => {
data[attribute] = this[attribute];
return data;
}, {});
}
post(endpoint) {
return this.submit(endpoint);
}
patch(endpoint) {
return this.submit(endpoint, 'patch');
}
delete(endpoint) {
return axios.delete(endpoint, {}, this.headers)
.catch(this.onFail.bind(this))
.then(this.onSuccess.bind(this));
}
submit(endpoint, requestType = 'post') {
this.form = new FormData();
this.form.append('_method', requestType);
this.buildFormData(this.data());
return axios.post(endpoint, this.form, {
headers: {
'Content-Type': `multipart/form-data; boundary=${this.form._boundary}`,
}
})
.catch(this.onFail.bind(this))
.then(this.onSuccess.bind(this));
}
onSuccess(response) {
this.submitted = true;
this.errors = {};
return response;
}
onFail(error) {
console.log(error);
this.errors = error.response.data.errors;
this.submitted = false;
throw error;
}
reset() {
Object.assign(this, this.originalData);
}
}
Edit Based on your note specifying you're using vue-upload-component
Your submit method should look like this
submitFile(){
let files = this.contract_file.map((obj) => obj.file));
let form = new MyFormData({files: files});
form.post('contracts/uploadfile/' + this.id_contract)
.then(function(){
//
})
.catch(function(){
//
});
},
In your controller
public function uploadFile(Request $request, Contract $contract) {
if($request->hasFile('files')){
$files = $request->file('files');
foreach ($files as $file) {
$filename = $file->getClientOriginalName();
$path = $file->store($contract->id,'uploads');
$contractFile = new ContractFile();
$contractFile->fill([
'contract_id' => $contract->id,
'name' => $filename,
'path' => $path,
])->save();
}
}
}
Adding the boundary to the Content-Type header fixed my problem. You can do it like below. Just change only submitFile() function.
submitFile(){
this.contract_file.forEach((file) =>{
let formData = new FormData();
formData.append('file', file.file);
axios.post('contracts/uploadfile/' + this.id_contract,
formData,
{
headers: {
'Content-Type': 'multipart/form-data;boundary=' + Math.random().toString().substr(2),
}
}
).then(function(){
//
})
.catch(function(){
//
});
})
},

Upload multiple file with vue js and axios

I am trying to upload multiple images using vuejs and axios but on server side i am getting empty object. I added multipart/form-data in header but still empty object.
submitFiles() {
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
console.log(file);
formData.append('files[' + i + ']', file);
}
/*`enter code here`
Make the request to the POST /file-drag-drop URL
*/
axios.post( '/fileupload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
},
}
).then(function(){
})
.catch(function(){
});
},
HTML:
<form method="post" action="#" id="" enctype="multipart/form-data">
<div class="form-group files text-center" ref="fileform">
<input type="file" multiple="multiple">
<span id='val'></span>
<a class="btn" #click="submitFiles()" id='button'>Upload Photo</a>
<h6>DRAG & DROP FILE HERE</h6>
</div>
My Server side code:
class FileSettingsController extends Controller
{
public function upload(Request $request){
return $request->all();
}
}
Output:
{files: [{}]}
files: [{}]
0: {}
Console.log() result:
File(2838972) {name: "540340.jpg", lastModified: 1525262356769, lastModifiedDate: Wed May 02 2018 17:29:16 GMT+0530 (India Standard Time), webkitRelativePath: "", size: 2838972, …}
You forgot to use $refs. Add ref to your input:
<input type="file" ref="file" multiple="multiple">
Next, access your files like this:
submitFiles() {
const formData = new FormData();
for (var i = 0; i < this.$refs.file.files.length; i++ ){
let file = this.$refs.file.files[i];
formData.append('files[' + i + ']', file);
}
axios.post('/fileupload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
}
).then(function(){
})
.catch(function(){
});
},
This should be works.
If anyone wondering, "How can I send also data with it", there you go:
formData.append('name[' + this.name + ']', name);
formData.getAll('files', 'name');
For Composition API this should work:
const files = ref([])
function save() {
let formData = new FormData()
for (let file of files.value) {
formData.append('files', file)
}
axios.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
})
}

Upload file with Vuejs

i'm working in Laravel. i need to upload file with Vuejs. but it's not working. I add this code:
Blade (File upload):
<input class="form-control" type="file" >
Script Vuejs :
var app = new Vue({
el: '#app',
data: {
person: {
id: 0,
user_name:'',
position_id:'',
image:'',
},
},
methods: {
addPerson: function () {
axios.post('/addperson', this.person)
.then(response => {
console.log(response.data);
if (response.data.etat) {
this.person = {
id: 0,
user_name: response.data.etat.user_name,
position_name: response.data.etat.position_id,
image: response.data.etat.image
};
}
})
.catch(error => {
console.log('errors: ', error)
})
},
Controller:
public function addPerson(Request $request){
$person = new Person;
$person->user_name=$request->user_name;
$person->position_id=$request->position_id;
if($request->hasFile('photo')){
$person->image= $request->image->store('image');
}
$person->save();
return back()->with('success', 'New Position added successfully.');
My Axios post function is working without the image upload line code. I just don't know how to add the upload code.
Thank you if someone can help.
In your blade file
<input type="file" #change="onFileChange" name="id_image" id="id_image" class="inputFile">
In your vue.js file, under methods:
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.createImage(files[0]);
},
createImage(file) {
let reader = new FileReader();
reader.onload = (e) => {
this.person.image = e.target.result;
};
reader.readAsDataURL(file);
},
That should allow your axios code to upload the image. Note, that it uploads in base64, so if you need validators you will have to create a custom Validator for base64 images.
I struggled to find out how to do this, but I've now found a way. Hopefully this makes someones life easier(I have the uploadUserImage method in a mixin):
HTML:
<input type="file" #change="uploadImage($event)">
JS:
uploadImage (e) {
this.file = e.currentTarget.files[0]
let formData = new FormData()
formData.append('img', this.file)
this.uploadUserImage(formData)
}
uploadUserImage: function (formData) {
axios.post('http://snowdon-backend.local:8000/api/users/img', formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(function (response) {
console.log(response)
})
}
Make sure file is set in the data method as well:
data () {
return {
file: ''
}
}

Resources