Multiple file upload using Laravel & Vue JS - javascript

so I've been trying to upload multiple image file using Vue JS with Laravel at server side.
My template vue
<input type="file" id = "file" ref="file" v-on:change="onImageChange" multiple />
My Javascript code
<script>
export default {
data(){
return{
product: {},
image: '',
}
},
created() {
let uri = `/api/product/edit/${this.$route.params.id}`;
this.axios.get(uri).then((response) => {
this.product = response.data;
});
},
methods:{
onImageChange(e){
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.createImage(files[0]);
},
createImage(file){
let reader = new FileReader();
let vm = this;
reader.onload = (e) => {
vm.image = e.target.result;
};
reader.readAsDataURL(file);
},
replaceByDefault(e) {
e.target.src = this.src='/uploads/products/default_image.jpg';
},
saveImage(e){
e.preventDefault()
var file = document.getElementById('file').files;
let formData = new FormData;
formData.append('productId', this.product.id)
formData.append('file', file[0])
axios.post('/api/product/image/add', formData, {
headers: {'Content-Type': 'multipart/form-data'}
}).then((response) => {
this.$router.push({name: 'view',params: { id: this.product.id }});
});
}
}
}
</script>
I saw somewhere the internet that in vue you can use looping the formData.append but how do i catch the data in the server side. Here is my ProductController
$saveImage = new Gallery;
$saveImage->product_id = $request->productId;
$file = request()->file('file');
$file_name = time().$file->getClientOriginalName();
$path = $imgUpload = Image::make($file)->save(public_path('/uploads/products/' . $file_name));
$saveImage->path = '/uploads/products/'.$file_name;
$saveImage->status = 1;
$saveImage->save();
return "success";
Thank you very much guys!

you can use request()->file('file') to get files. but you have to add some changes in your vue source when you are trying to send an array of files.
Vue
let formData = new FormData;
formData.append('productId', this.product.id)
// append files
for(let i=0; i<file.length; i++){
formData.append('file[]', file[i])
}
useing file[] instead of file will generate an array of files in request payload.
then in laravel side of code you can use request()->file('file') to get that array of files. but if you want just one of them (for example: first one) you can use request()->file('file.0') to get that file.

Related

How to change file reference strings to use generated hashes from copy file webpack plugin

I'm copying all my assets to my build directory using copy webpack plugin. It generates all the files with the hashes properly but is there a simple way of changing any of these file references in code with the new hashed filename?
new CopyWebpackPlugin({
patterns: [
{ from: 'src/assets', to: '[path][name].[contenthash][ext]' },
],
}),
Im currently using strings like below to load assets into phaser, using a query param to break the cache but i want the url to be updated with the new hash filename so I don't need to use the query param to bust the server cache and can take advantage of server caching.
{
key: 'atlas',
url: `assets/img/atlas.json?t=${new Date().getTime().toString()}`,
path: './assets/img/'
},
{
key: 'atlas',
url: `assets/img/atlas.json`,
path: './assets/img/'
},
so Im hoping the above will look like this in the output after webpack has run
{
key: 'atlas',
url: `assets/img/atlas.{generatedHash}.json`,
path: './assets/img/'
},
Edit:
Okay so I accomplished the above by using webpack-asset-manifest
new ManifestPlugin({
// publicPath: 'assets/',
}),
and then having a function for getting my urls from the generated json after it is loaded
protected getAssetFromKey(key: string | Array<string>): string | Array<string> {
if (Array.isArray(key)) {
let urls = new Array<string>();
key.forEach((urlKey: string) => {
urls.push(this.assetKeys[urlKey]);
});
return urls;
} else {
return this.assetKeys[key];
}
}
But now I have hit an issue where the json atlas files are pointing to the old images and there seems to be no easy way to edit these. Im thinking of something like string replace loader but Im wondering if there is a better way and I am unsure of how to replace the string with a value from the manifest.json that is exported by webpack.
Okay So I figured this out by adding the manifest plugin to my webpack config which generates a json file with all the original file names as keys and all the new hashed filenames as values.
new ManifestPlugin({
}),
and then I added in a compiler hook in the plugins area that does reads all the atlas files and replaces the strings in them, as well as any references to the assets in the compiled code.
https://webpack.js.org/api/compiler-hooks/
{
apply: (compiler) => {
compiler.hooks.done.tap("update links", (stats) => {
Fs.readJson('dist/assets-manifest.json').then( (value) => {
let keys = Object.keys(value)
let files = Object.values(value);
files.forEach( (file, index) => {
if( file.includes('json')) {
let splitString = keys[index].split('/');
let findFile = splitString[splitString.length-1].replace('.json', '');
console.log(`find file- ${findFile}`);
let replaceWithString = '';
let replaceString = ''
for( let i =0 ; i < keys.length; i++) {
if( keys[i].includes(`${findFile}`) && keys[i].includes('.webp') ) {
console.log(keys[i]);
let splitFiles = files[i].split('/');
let splitKeys = keys[i].split('/');
replaceWithString = splitFiles[splitFiles.length-1];
replaceString = splitKeys[splitKeys.length-1];
break;
}
}
console.log( `REPLACE WITH STRING = ${replaceWithString}`)
console.log(`reading file-${file}`);
Fs.readJson(`dist/${file}`).then( (val) => {
let stringJson = JSON.stringify(val);
console.log(`replacing ${replaceString} with ${replaceWithString}`);
let result = stringJson.replace(replaceString, replaceWithString);
let outputJson = JSON.parse(result);
Fs.writeJson(`dist/${file}`, outputJson, 'utf8').then( () => {
console.log( `!!!!! SUCCESS !!!!!`);
});
});
}
});
files.forEach( (file) => {
if( file.includes('.js') && !file.includes('json') ) {
console.log('FILE: ' + file)
Fs.content(`dist/${file}`).then( ( val) => {
keys.forEach( (key,index) => {
if( key.includes('assets/')) {
val = val.replaceAll(key, files[index]);
console.log(`REPLACING: ${key} with ${files[index]} in ${file}`)
}
});
Fs.writeFile(`dist/${file}`, val).then( () => {
console.log("--SUCCESS---")
});
});
}
})
}).catch( (err) => {
console.log(`error ${err}`);
})
});
}
},

Upload Multiple images in Laravel using Vue.js

I'm trying to upload image using vue.js in Laravel for that i'm using this link
https://jsfiddle.net/b412ruzo/
to upload image using vue.js and when i submit the form i'm getting following image in files array
now issue is that i cannot get this file array in laravel controller
when i print
$request->file('files')
in my controller i am getting null.
and when i print $request->input('files') this is the result, an empty array
Any help regarding this issue is highly appreciated.
Code Snippet :
data() {
return {
rawData: [],
formData: new Form({
files:[],
})
..
const header = {
Authorization: "Bearer " + this.token,
};
this.formData
.post(APP_URL + `/api/post`, { headers: header })
.then((response) => {
}
not sure you can send ajax request via this.formData.post
try this
new Vue({
el: "#app",
data() {
return {
option: {
maxFileCount: 3
},
files:[],
rawData: [],
}
},
methods: {
loaddropfile: function(e) {
e.preventDefault()
e.stopPropagation()
alert('ok')
console.log(e)
},
openinput: function() {
document.getElementById("vue-file-upload-input").click();
},
addImage: function(e) {
const tmpFiles = e.target.files
if (tmpFiles.length === 0) {
return false;
}
const file = tmpFiles[0]
this.files.push(file)
const self = this
const reader = new FileReader()
reader.onload = function(e) {
self.rawData.push(e.target.result)
}
reader.readAsDataURL(file)
},
removeFile: function(index) {
this.files.splice(index, 1)
this.rawData.splice(index, 1)
document.getElementById("vue-file-upload-input").value = null
},
upload: function() {
alert('Check console to see uploads')
console.log(this.files)
axios.post(`${APP_URL}/api/post`,{files:this.files},{ headers: header })
.then((response) => {});
}
},
mounted(){
}
})
it will send your form data to files key so you can get all the files via $request->file('files')

How to call a function inside a vue npm package?

What I'm trying to do is to display and modify the images that the car has "in my case", so I used the vue-upload-multiple-image package to save the images and went well, but when I call back these images to the same package I got stuck.
I convert the images that has been stored to base64 now what I want is the list of images go to specific function inside that package, so it will display the images when I try to update the car.
This is the function I want to call:
createImage(file) {
let reader = new FileReader()
let formData = new FormData()
formData.append('file', file)
reader.onload = e => {
let dataURI = e.target.result
if (dataURI) {
if (!this.images.length) {
this.images.push({
name: file.name,
path: dataURI,
highlight: 1,
default: 1,
})
this.currentIndexImage = 0
} else {
this.images.push({
name: file.name,
path: dataURI,
highlight: 0,
default: 0,
})
}
this.$emit(
'upload-success',
formData,
this.images.length - 1,
this.images,
)
}
}
reader.readAsDataURL(file)
},
The Function inside this file
I tried to console.log the function normally it outputs undefined,
I think of props but how it gonna help me.
mounted(){
console.log(this.createImage);
What I want is just to call this function inside my editcar component and sent to it the converter images.
Thank you for helping me and read the this far.
I Found the solution of the problem: in the doc there is dataImages prop. I use it like this:
<div class="form-group m-form__group">
<vue-upload-multiple-image
#upload-success="uploadImageSuccess"
#before-remove="beforeRemove"
#edit-image="editImage"
:dataImages="images"
></vue-upload-multiple-image>
</div>
And it must the images base64 so here is the function the the data.
data() {
return {
images :[],
}
},
mounted() {
this.ConvertImages();
},
This is methods:
methods: {
ConvertImages() {
let images = this.car.images
let image = this.images
for (var i = 0; i < images.length; i++) {
this.toDataURL(images[i].path, function(dataURL) {
image.push({
path: dataURL,
})
})
}
},
toDataURL(url, callback) {
var xhr = new XMLHttpRequest()
xhr.onload = function() {
var reader = new FileReader()
reader.onloadend = function() {
callback(reader.result)
}
reader.readAsDataURL(xhr.response)
}
xhr.open('GET', url)
xhr.responseType = 'blob'
xhr.send()
},
}, //END OF METHODS

Upload file with Primeng upload component

I would upload the file in Angular using upload component
Here's my HTML:
<p-fileUpload mode="basic" name="demo[]" customUpload="true" accept="image/*" maxFileSize="1000000" (uploadHandler)="upload($event)"></p-fileUpload>
in my ts I print param value
upload(event) {
console.log(event)
}
I get only metadata and not blob content
{"files":[{"objectURL":{"changingThisBreaksApplicationSecurity":"blob:https://prime-ng-file-uploading.stackblitz.io/d429e761-c391-45fa-8628-39b603e25225"}}]}
I would also get file content to send via API to the server
Here's a stackblitz demo
In the official documentation you have an example:
export class FileUploadDemo {
uploadedFiles: any[] = [];
constructor(private messageService: MessageService) {}
onUpload(event) {
for (let file of event.files) {
this.uploadedFiles.push(file);
}
this.messageService.add({
severity: 'info',
summary: 'File Uploaded',
detail: ''
});
}
}
When I used primeNG, I did it like this (for uploading only 1 file) :
HTML
<p-fileUpload name="myfile[]" customUpload="true" multiple="multiple" (uploadHandler)="onUpload($event)" accept="application/pdf"></p-fileUpload>
component.ts
export class AlteracionFormComponent {
uplo: File;
constructor(private fileService: FileUploadClientService) {}
onUpload(event) {
for (let file of event.files) {
this.uplo = file;
}
this.uploadFileToActivity();
}
uploadFileToActivity() {
this.fileService.postFile(this.uplo).subscribe(data => {
alert('Success');
}, error => {
console.log(error);
});
}
}
And my service (in Angular)
service.ts
postFile(id_alteracion: string, filesToUpload: FileUploadModel[], catalogacion: any): Observable<any> {
let url = urlAPIAlteraciones + '/';
url += id_alteracion + '/documentos';
const formData: FormData = new FormData();
formData.append('json', JSON.stringify(catalogacion));
for (let file of filesToUpload) {
formData.append('documento', file.data, file.data.name);
}
console.log(formData);
let headers = new HttpHeaders();
return this._http.post(url, formData, { headers: headers });
}
Hope that helps
I'm trying to use the standard PrimeNG approach for uploading multiple files at once.
<p-fileUpload name="myfile[]" [url]="MyApiUrl" multiple="multiple"></p-fileUpload>
In my ApiController, I get the following:
public List<FileModel> Post()
{
var files = HttpContext.Current.Request.Files;
var result = new List<FileModel>();
if (files.Count > 0)
{
Guid guid = Guid.NewGuid();
var path = System.IO.Path.Combine(System.Web.Configuration.WebConfigurationManager.AppSettings["UploadFileLocation"], guid.ToString() + "/");
System.Web.HttpContext.Current.Session["guid"] = guid;
if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path); }
foreach (string fileName in files.AllKeys)
{
var file = files[fileName];
var filePath = path + file.FileName;
file.SaveAs(filePath);
result.Add(_data.InsertFile(FileModel.Create(path, file.FileName)));
}
}
return result;
}
The problem I'm having is that "fileName" in var file = files[fileName] always equals the first file in the group of files uploaded. I don't know if this is an issue with HttpContext.Current.Request.Files or a PrimeNG bug. Has anyone ran into this type of problem?
Doing it this way because I want to store the guid in my session and grab it later on when the form is submitted...

How to test file inputs with Cypress?

How can I write an e2e test of flow that requires interaction with the file Input DOM element?
If it's a text input I can interact with it (check value, set value) etc as its a DOM component. But If I have a File Input element, I am guessing that the interaction is limited till I can open the dialog to select a File. I can't move forward and select the file I want to upload as the dialog would be native and not some browser element.
So how would I test that a user can correctly upload a file from my site? I am using Cypress to write my e2e tests.
it('Testing picture uploading', () => {
cy.fixture('testPicture.png').then(fileContent => {
cy.get('input[type="file"]').attachFile({
fileContent: fileContent.toString(),
fileName: 'testPicture.png',
mimeType: 'image/png'
});
});
});
Use cypress file upload package: https://www.npmjs.com/package/cypress-file-upload
Note: testPicture.png must be in fixture folder of cypress
For me the easier way to do this is using this cypress file upload package
Install it:
npm install --save-dev cypress-file-upload
Then add this line to your project's cypress/support/commands.js:
import 'cypress-file-upload';
Now you can do:
const fixtureFile = 'photo.png';
cy.get('[data-cy="file-input"]').attachFile(fixtureFile);
photo.png must be in cypress/fixtures/
For more examples checkout the Usage section on README of the package.
Since 9.3.0 you can use selectFile.
cy.get('input[type=file]').selectFile('cypress/fixtures/file.json')
See:
https://github.com/cypress-io/cypress/issues/170
https://cypress.io/blog/2022/01/18/uploading-files-with-selectfile/
https://docs.cypress.io/guides/references/migration-guide#Migrating-from-cypress-file-upload-to-selectFile
With this approach/hack you can actually make it:
https://github.com/javieraviles/cypress-upload-file-post-form
It is based on different answers from the aformentioned thread https://github.com/cypress-io/cypress/issues/170
First scenario (upload_file_to_form_spec.js):
I want to test a UI where a file has to be selected/uploaded before
submitting the form.
Include the following code in your "commands.js" file within the cypress
support folder, so the command cy.upload_file() can be used from any test:
Cypress.Commands.add('upload_file', (fileName, fileType, selector) => {
cy.get(selector).then(subject => {
cy.fixture(fileName, 'hex').then((fileHex) => {
const fileBytes = hexStringToByte(fileHex);
const testFile = new File([fileBytes], fileName, {
type: fileType
});
const dataTransfer = new DataTransfer()
const el = subject[0]
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
})
})
})
// UTILS
function hexStringToByte(str) {
if (!str) {
return new Uint8Array();
}
var a = [];
for (var i = 0, len = str.length; i < len; i += 2) {
a.push(parseInt(str.substr(i, 2), 16));
}
return new Uint8Array(a);
}
Then, in case you want to upload an excel file, fill in other inputs and submit the form, the test would be something like this:
describe('Testing the excel form', function () {
it ('Uploading the right file imports data from the excel successfully', function() {
const testUrl = 'http://localhost:3000/excel_form';
const fileName = 'your_file_name.xlsx';
const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const fileInput = 'input[type=file]';
cy.visit(testUrl);
cy.upload_file(fileName, fileType, fileInput);
cy.get('#other_form_input2').type('input_content2');
.
.
.
cy.get('button').contains('Submit').click();
cy.get('.result-dialog').should('contain', 'X elements from the excel where successfully imported');
})
})
Testing File Input elements is not yet supported in Cypress. The only way to test File Inputs is to:
Issue native events (which Cypress has on their Roadmap).
Understand how your application handles file uploads with File API and then stub it out. It's possible but not generic enough to give any specific advice on.
See this open issue for more detail.
In my case I had client & server side file validation to check if the file is JPEG or PDF. So I had to create a upload command which would read the file in binary from Fixtures and prepare a blob with the file extension.
Cypress.Commands.add('uploadFile', { prevSubject: true }, (subject, fileName, fileType = '') => {
cy.fixture(fileName,'binary').then(content => {
return Cypress.Blob.binaryStringToBlob(content, fileType).then(blob => {
const el = subject[0];
const testFile = new File([blob], fileName, {type: fileType});
const dataTransfer = new DataTransfer();
dataTransfer.items.add(testFile);
el.files = dataTransfer.files;
cy.wrap(subject).trigger('change', { force: true });
});
});
});
then use it as
cy.get('input[type=file]').uploadFile('smiling_pic.jpg', 'image/jpeg');
smiling_pic.jpg will be in fixtures folder
The following function works for me,
cy.getTestElement('testUploadFront').should('exist');
const fixturePath = 'test.png';
const mimeType = 'application/png';
const filename = 'test.png';
cy.getTestElement('testUploadFrontID')
.get('input[type=file')
.eq(0)
.then(subject => {
cy.fixture(fixturePath, 'base64').then(front => {
Cypress.Blob.base64StringToBlob(front, mimeType).then(function(blob) {
var testfile = new File([blob], filename, { type: mimeType });
var dataTransfer = new DataTransfer();
var fileInput = subject[0];
dataTransfer.items.add(testfile);
fileInput.files = dataTransfer.files;
cy.wrap(subject).trigger('change', { force: true });
});
});
});
// Cypress.Commands.add(`getTestElement`, selector =>
// cy.get(`[data-testid="${selector}"]`)
// );
Also based on previously mentioned github issue, so big thanks to the folks there.
The upvoted answer worked initially for me, but I ran into string decoding issues trying to handle JSON files. It also felt like extra work having to deal with hex.
The code below handles JSON files slightly differently to prevent encode/decode issues, and uses Cypress's built in Cypress.Blob.base64StringToBlob:
/**
* Converts Cypress fixtures, including JSON, to a Blob. All file types are
* converted to base64 then converted to a Blob using Cypress
* expect application/json. Json files are just stringified then converted to
* a blob (prevents issues with invalid string decoding).
* #param {String} fileUrl - The file url to upload
* #param {String} type - content type of the uploaded file
* #return {Promise} Resolves with blob containing fixture contents
*/
function getFixtureBlob(fileUrl, type) {
return type === 'application/json'
? cy
.fixture(fileUrl)
.then(JSON.stringify)
.then(jsonStr => new Blob([jsonStr], { type: 'application/json' }))
: cy.fixture(fileUrl, 'base64').then(Cypress.Blob.base64StringToBlob)
}
/**
* Uploads a file to an input
* #memberOf Cypress.Chainable#
* #name uploadFile
* #function
* #param {String} selector - element to target
* #param {String} fileUrl - The file url to upload
* #param {String} type - content type of the uploaded file
*/
Cypress.Commands.add('uploadFile', (selector, fileUrl, type = '') => {
return cy.get(selector).then(subject => {
return getFixtureBlob(fileUrl, type).then(blob => {
return cy.window().then(win => {
const el = subject[0]
const nameSegments = fileUrl.split('/')
const name = nameSegments[nameSegments.length - 1]
const testFile = new win.File([blob], name, { type })
const dataTransfer = new win.DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
return subject
})
})
})
})
You can do it with new Cypress command:
cy.get('input[type=file]').selectFile('file.json')
This is now available within Cypress library itself from version 9.3 and above. Follow the migration guide on how to move from cypress-file-upload plugin to Cypress .selectFile() command:
Migrating-from-cypress-file-upload-to-selectFile
in your commands.ts file within your test folder add:
//this is for typescript intellisense to recognize new command
declare namespace Cypress {
interface Chainable<Subject> {
attach_file(value: string, fileType: string): Chainable<Subject>;
}
}
//new command
Cypress.Commands.add(
'attach_file',
{
prevSubject: 'element',
},
(input, fileName, fileType) => {
cy.fixture(fileName)
.then((content) => Cypress.Blob.base64StringToBlob(content, fileType))
.then((blob) => {
const testFile = new File([blob], fileName);
const dataTransfer = new DataTransfer();
dataTransfer.items.add(testFile);
input[0].files = dataTransfer.files;
return input;
});
},
);
Usage:
cy.get('[data-cy=upload_button_input]')
.attach_file('./food.jpg', 'image/jpg')
.trigger('change', { force: true });
another option is to use cypress-file-upload, which is buggy in version 4.0.7 (uploads files twice)
cy.fixture("image.jpg").then((fileContent) => {
cy.get("#fsp-fileUpload").attachFile({
fileContent,
fileName: "image",
encoding: "base64",
mimeType: "image/jpg",
});
});
Here is the multiple file upload version:
Cypress.Commands.add('uploadMultiFiles',(args) => {
const { dataJson, dirName, inputTag, mineType} = args
const arr = []
dataJson.files.forEach((file, i) => {
cy.fixture(`${ dirName + file }`).as(`file${i}`)
})
cy.get(`${inputTag}`).then(function (el) {
for(const prop in this) {
if (prop.includes("file")) {
arr.push(this[prop])
}
}
const list = new DataTransfer()
dataJson.files.forEach((item, i) => {
// convert the logo base64 string to a blob
const blob = Cypress.Blob.base64StringToBlob(arr[i], mineType)
const file = new FileCopy([blob], `${item}`, { type: mineType }, `${ dirName + item }`)
const pathName = dirName.slice(1)
file.webkitRelativePath = `${ pathName + item}`
console.log(file)
list.items.add(file)
})
const myFileList = list.files
el[0].files = myFileList
el[0].dispatchEvent(new Event('change', { bubbles: true }))
})
})
The usage:
First, prepare a data.json file inside the fixtures folder, example:
data.json
{
"files":[
"1_TEST-JOHN-01.jpeg",
"2_TEST-JOHN-01.jpeg",
"3_TEST-JOHN-01.jpeg",
"4_TEST-JOHN-01.jpeg",
"5_TEST-JOHN-01.jpeg",
"6_TEST-JOHN-01.jpeg",
"7_TEST-JOHN-01.jpeg",
"8_TEST-JOHN-01.jpeg",
"9_TEST-JOHN-01.jpeg",
"10_TEST-JOHN-01.jpeg"
]
}
Second, import the json data into your spec.js
import data from '../fixtures/data.json'
Third, Write a class to extend the File web API object with the functions to set and get webkitRelativePath value
class FileCopy extends File {
constructor(bits, filename, options) {
super(bits, filename, options)
let webkitRelativePath
Object.defineProperties(this, {
webkitRelativePath : {
enumerable : true,
set : function(value){
webkitRelativePath = value;
},
get : function(){
return webkitRelativePath;
}
},
});
}
}
Finally, call the cmd in the spec.js
cy.uploadMultiFiles(
{
dataJson:data, // the data.json you imported.
dirName:"/your/dirname/",
inputTag:"input#upload",
mineType:"image/jpeg"
}
)
if your file input display: none; use
cy.get('[data-type=inputFile]').selectFile('cypress/fixtures/avatar.jpg', { force: true })
else
cy.get('[data-type=inputFile]').selectFile('cypress/fixtures/avatar.jpg')

Categories