HTML 5 Upload files using drag and drop - ajax

I would like to upload files using a drag and drop
I'm using laravel framework 5.4 and JS. The problem is with what happens after the upload is complete. I can see the file is uploaded to the folder but can't get the name of the file or any reference to it.
This is my view.. I'm uploading files to send as email attachments
{!! Form::open([
'url' => 'send',
'files' => true,
'id'=>'upload',
'enctype'=> 'multipart/form-data'
]) !!}
<div class="box-body">
<div class="form-group">
{!! Form::text('to', null, ['class' => 'form-control', 'placeholder' => 'Send to']) !!}
</div>
<div class="form-group">
{!! Form::text('subject', null, ['class' => 'form-control', 'placeholder' => 'Subject']) !!}
</div>
<div class="form-group">
{!! Form::textarea('content', null, ['class' => 'form-control message-body wysihtml5-sandbox', 'placeholder' => 'Message']) !!}
</div>
<div class="form-group">
<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="320000" />
<div>
<label for="fileselect">Files to upload:</label>
<input type="file" id="fileselect" name="fileselect[]" multiple="multiple" />
<div id="filedrag">or drop files here</div>
</div>
<div id="progress"></div>
<div id="messages">
</div>
</div><!-- /.box-body -->
<div class="box-footer">
<div class="pull-right">
{{--<button class="btn btn-default"><i class="fa fa-pencil"></i> Draft</button>--}}
{!! Form::submit('Send', ['class' => 'btn btn-primary submit']) !!}
</div>
<div class="form-group">
<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="320000" />
<div>
<label for="fileselect">Files to upload:</label>
<input type="file" id="fileselect" name="fileselect[]" multiple="multiple" />
<div id="filedrag">or drop files here</div>
</div>
<div id="progress"></div>
<div id="messages">
</div>
</div><!-- /.box-body -->
<div class="box-footer">
{!! Form::submit('Send', ['class' => 'btn btn-primary submit']) !!}
The scripts I'm using
(function() {
var send = XMLHttpRequest.prototype.send,
token = $('meta[name=csrf-token]').attr('content');
XMLHttpRequest.prototype.send = function(data) {
this.setRequestHeader('X-CSRF-Token', token);
return send.apply(this, arguments);
}
// getElementById
function $id(id) {
return document.getElementById(id);
}
// output information
function Output(msg) {
var m = $id("messages");
m.innerHTML = msg + m.innerHTML;
}
// file drag hover
function FileDragHover(e) {
e.stopPropagation();
e.preventDefault();
e.target.className = (e.type == "dragover" ? "hover" : "");
}
// file selection
function FileSelectHandler(e) {
// cancel event and hover styling
FileDragHover(e);
// fetch FileList object
var files = e.target.files || e.dataTransfer.files;
// process all File objects
for (var i = 0, f; f = files[i]; i++) {
UploadFile(f);
}
}
function UploadFile(file) {
// following line is not necessary: prevents running on SitePoint servers
var xhr = new XMLHttpRequest();
if (xhr.upload && file.size <= $id("MAX_FILE_SIZE").value) {
// create progress bar
var o = $id("progress");
var progress = o.appendChild(document.createElement("p"));
progress.appendChild(document.createTextNode("upload " + file.name));
// progress bar
xhr.upload.addEventListener("progress", function (e) {
var pc = parseInt(100 - (e.loaded / e.total * 100));
progress.style.backgroundPosition = pc + "% 0";
}, false);
// file received/failed
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4) {
progress.className = (xhr.status == 200 ? "success" : "failure");
}
};
// start upload
xhr.open("POST", '/getAttachments', true);
xhr.setRequestHeader("X_FILENAME", file.name);
xhr.send(file);
}
}
// initialize
function Init() {
var fileselect = $id("fileselect"),
filedrag = $id("filedrag"),
submitbutton = $id("submitbutton");
// file select
fileselect.addEventListener("change", FileSelectHandler, false);
// is XHR2 available?
var xhr = new XMLHttpRequest();
if (xhr.upload) {
// file drop
filedrag.addEventListener("dragover", FileDragHover, false);
filedrag.addEventListener("dragleave", FileDragHover, false);
filedrag.addEventListener("drop", FileSelectHandler, false);
filedrag.style.display = "block";
}
}
// call initialization file
if (window.File && window.FileList && window.FileReader) {
Init();
}
})();
The script I used was using the form action attribute to upload the files. Since the form action is to send the email, I routed xhr.open("POST", '/getAttachments', true) to a different controller method that I call in the send method.
My controller methods #getAttachments and #send
public function getAttachments()
if ($fn) {
// AJAX call
file_put_contents(
'uploads/' . $fn,
file_get_contents('php://input')
);
return $fn ; // HERE $fn = false though the name of the file stored is correct
}}
and I tried
Storage::put($fn, file_get_contents('php://input'));
$file = Storage::get($fn) ; return $file;);
public function send( Request $request ) {
$file = $this->getAttachments();
// $file = $false}
I figured it is returning false because if I hit send the page refreshes
and $_SERVER['HTTP_X_FILENAME'] variable is lost, so I tried saving it to the session but no use.. Can't get the files
public function getAttachments()
{
$fn = (isset($_SERVER['HTTP_X_FILENAME']) ? $_SERVER['HTTP_X_FILENAME'] : false);
if ($fn) {
session(['attachments' => $fn]);
Storage::put($fn, file_get_contents('php://input'));
}
}
public function send( Request $request ) {
//Grab uploaded file
if ($request->session()->exists('attachments')) {
$attachments = $request->session()->pull('attachments');
$files = Storage::get($attachments);
}
Don't know why but it doesn't store to the session either.

Not sure what exactly you are trying to do. But if you to want to upload a file by drag and drop then I would suggest you use this awesome js:
http://www.dropzonejs.com/
If you would like to see the implementation, then you can check it over here:
https://github.com/xparthx/Laravel-AWS-S3

Related

How could I preview an image without storing in aws s3?

My question is: Should I create 2 folders: one for temporary and another one for original? I'm using a component for uploading images. When I upload an image, It is stored in s3 and it is retrieved again to be shown in a small image. My problem here is If I upload 1 image and then I decide to upload another one, I uploaded 2 files in the S3 bucket and so on ... without pressing add category.
Finally: I'm using Vue 2 and Laravel 7.x, thanks.
I'm not so familiar with storing process but I need help to decide what is more efficient, thanks.
category.vue
<template>
<Modal v-model="addModal" :closable="false" :mask-closable="false" title="Add a category">
<Input v-model="data.categoryName" placeholder="Add category..."/>
<div class="space"></div>
<Upload ref="uploads" action="/app/upload" :format="['jpg','jpeg','png']" :
headers="{'x-csrf-token': token, 'X-Requested-With': 'XMLHttpRequest'}" :max-size="2048"
:on-error="handleError" :on-exceeded-size="handleMaxSize" :on-format-error="handleFormatError"
:on-success="handleSuccess" type="drag">
<div style="padding: 20px 0">
<Icon size="52" style="color: #3399ff" type="ios-cloud-upload"></Icon>
<p> Click or drag files here to upload </p>
</div>
</Upload>
<div v-if="data.iconImage" class="demo-upload-list">
<img :src="`${data.iconImage}`"/>
<div class="demo-upload-list-cover">
<Icon type="ios-trash-outline" #click="deleteImage"></Icon>
</div>
</div>
<div slot="footer">
<Button type="default" #click="addModal=false">Close</Button>
<Button :disabled="isAdding" :loading="isAdding" type="primary" #click="addCategory">
{{ isAdding ? 'Adding ..' : 'Add a category' }}
</Button>
</div>
</Modal>
</template>
<script>
// Other parts of this section are removed for clarity
export default {
methods: {
async addCategory() {
console.log('1', this.data.iconImage)
if (this.data.categoryName.trim() === '') return this.e('This Category is required')
if (this.data.iconImage.trim() === '') return this.e('This Icon Image is required')
this.data.iconImage = `${this.data.iconImage}`
console.log('2', this.data.iconImage)
const res = await this.callApi('post', 'app/create_category', this.data)
if (res.status === 200) {
this.categoryLists.unshift(res.data)
this.s('Category has been succesfully')
this.addModal = false
this.data.categoryName = ''
this.data.iconImage = ''
this.$refs.uploads.clearFiles()
} else {
if (res.status === 422) {
if (res.data.errors.categoryName) {
this.e(res.data.errors.categoryName[0])
}
if (res.data.errors.iconImage) {
this.e(res.data.errors.iconImage[0])
}
} else {
this.swr()
}
}
},
},
}
</script>
My controller for addCategory
public function addCategory(Request $request)
{
$this->validate($request, [
'categoryName' => 'required',
'iconImage' => 'required',
]);
$category = new Category();
$category->categoryName = $request->categoryName;
$category->iconImage = $request->iconImage;
$category->save();
return response()->json($category);
}

How do I get my variable to show in my store function

This is probably a very simple thing, but for some reason I just can't figure it out. I created a function that gets the images from my vue component.
What I'm trying to do is take the images from my postImage() and have them in my store() function, so that I can save everything into the database.
The problem I'm getting is when I do that I get this error
Too few arguments to function App\Http\Controllers\Admin\CategoryController::store(), 1 passed and exactly 2 expected
I do understand that the error is telling me that only the $request was sent and not the $image. I'm not sure how to get it working. If I've left anything out please let me know
Here is my controller
public function store(Request $request, $image)
{
$category = new Category();
$input = $this->safeInput($request);
$category->fill($input);
dd($image);
$slug = $category->slug($category->title);
$category->slug = $slug;
if($request->has('active'))
{
$category->active = 1;
}else{
$category->active = 0;
}
$category_order = $category->order_number();
$category->order = $category_order;
$category->save();
}
public function postImage(Request $request)
{
if($request->hasFile('image'))
{
$names = [];
foreach($request->file('image') as $image)
{
$destinationPath = 'product_images/category/';
$filename = $image->getClientOriginalName();
$image->move($destinationPath, $filename);
array_push($names, $filename);
}
$image = json_encode($names);
return $image;
}
}
This is my vue component
<template>
<div class="container">
<div class="uploader"
#dragenter="OnDragEnter"
#dragleave="OnDragLeave"
#dragover.prevent
#drop="onDrop"
>
<div v-show="!images.length" :value="testing()">
<i class="fas fa-cloud-upload-alt"></i>
<div>OR</div>
<div class="file-input">
<label for="file">Select a file</label>
<input type="file" id="file" #change="onInputChange" multiple>
</div>
</div>
<div class="images-preview" v-show="images.length">
<div class="img-wrapper" v-for="(image, index) in images">
<img :src="image" :alt="`Image Uplaoder ${index}`">
<div class="details">
<span class="name" v-text="files[index].name"></span>
<span class="size" v-text="getFileSize(files[index].size)"></span>
</div>
<div class="btn btn-danger" #click="funDeleteFile(index)">
Remove
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
},
data() {
return {
isDragging: false,
//Sets the dragCount to 0
dragCount: 0,
//Makes files an array, so that we can send the files to the server
files: [],
//Makes images an array, so that we can let the user see the images
images: [],
}
},
methods: {
testing() {
console.log('This is submit images - '+this.files);
var formData = new FormData();
this.files.forEach(file => {
formData.append('image[]', file, file.name);
});
axios.post('/admin/category/post-image', formData);
},
OnDragEnter(e) {
//Prevents the default action of the browser
e.preventDefault();
// This lets the dragCount become 1, so that the image uploader changes colour
this.dragCount++;
// Changes the isDragging variable to true instead of false
this.isDragging = true;
return false;
},
OnDragLeave(e) {
//Prevents the default action of the browser
e.preventDefault();
// This lets the dragcount become 0, so that the image uploader changes to it's original colour
this.dragCount--;
// This is if the dragCount is <= 0 then the isDragging variable is false
if (this.dragCount <= 0)
this.isDragging = false;
},
onInputChange(e) {
// Grabs the files from the event
const files = e.target.files;
// Creates an array for files, so that we can loop thru it
// Send the file to the addImage method via "this.addImage(file)"
Array.from(files).forEach(file => this.addImage(file));
},
onDrop(e) {
//Prevents the default action of the browser
e.preventDefault();
//Stops the propagation into the other elements inside the one we drop and file into
e.stopPropagation();
// This is to disable the dragging of the images
this.isDragging = false;
// Grabs the files from the event
const files = e.dataTransfer.files;
// Creates an array for files, so that we can loop thru it
// Send the file to the addImage method via "this.addImage(file)"
Array.from(files).forEach(file => this.addImage(file));
},
addImage(file) {
//Checks if the file type is an image
if (!file.type.match('image.*')) {
this.$toastr.e(`${file.name} is not an image`);
return;
}
this.files.push(file);
const img = new Image(),
reader = new FileReader();
reader.onload = (e) => this.images.push(e.target.result);
reader.readAsDataURL(file);
},
}
}
</script>
my create.blade.php
#extends('layouts.admin')
#section('content')
#component('admin.components.products.category-form', [
'formUrl' => route('category.store'),
'formMethod' => 'POST',
'model' => $category,
'category_id' => $category_id,
'image' => '',
'image2' => ''
])
#endcomponent
#endsection
and my form
{{ Form::model($model, array('url' => $formUrl, 'method' => $formMethod, 'class' => 'add-form', 'files' => true)) }}
<div class="form-group">
{{ Form::label('category_id', 'Parent Category') }}
{{ Form::select('category_id', $category_id->prepend('Please Select', '0'), null, array('class' => 'form-control')) }}
</div>
<div class="form-group">
{{ Form::label('title', 'Title') }}
{{ Form::text('title', null, array('class' => 'form-control')) }}
</div>
<div class="form-group">
<label>Active:</label>
{{ Form::checkbox('active', 0) }}
</div>
<div id="app" class="mb-20">
<category-image></category-image>
</div>
<div class="form-group">
{{ Form::submit('Save', array('class' => "btn btn-dark btn-lg btn-block")) }}
</div>
{{ Form::close() }}
My routes
Route::resource('admin/category', 'Admin\CategoryController');
Route::post('admin/category/post-image', 'Admin\CategoryController#postImage')->name('admin.category.post-image');
UPDATE
I've tried this to pass the image to a hidden field in my form so that I can grab it in the $request in my store function.
In my CategoryController#create
$category = new Category();
$category_list = Category::with('parentCategory')->get();
$category_id = Category::pluck('title', 'id');
// I've added this.
$image = '';
return view('admin.products.category.create', compact('category', 'category_list', 'category_id', 'image'));
in my CategoryController#postImage
//I've added this to, so that I can pass the image variable to the create.blade.php
return redirect()->route('category.create', compact('image'));
then in my create.blade.php I added
'my_image' => $my_image
and in my category-form.blade.php component I added
<div id="app" class="mb-20">
<category-image></category-image>
<input type="hidden" name="image" id="image" value="{{ $my_image }}">
</div>
at the moment I haven't been able to do that either. Though I'm not sure if this is the right way to go, I'm a bit worried that some random person can then add whatever they want by using the hidden input
For what do you have the parameter $image? This is not specified in your axios.post.
public function store(Request $request)
{
$category = new Category();
$input = $this->safeInput($request);
$category->fill($input);
dd($this->postImage($request));
$slug = $category->slug($category->title);
$category->slug = $slug;
if($request->has('active'))
{
$category->active = 1;
}else{
$category->active = 0;
}
$category_order = $category->order_number();
$category->order = $category_order;
$category->save();
}
public function postImage($request)
{
if($request->hasFile('image'))
{
$names = [];
foreach($request->file('image') as $image)
{
$destinationPath = 'product_images/category/';
$filename = $image->getClientOriginalName();
$image->move($destinationPath, $filename);
array_push($names, $filename);
}
$image = json_encode($names);
return $image;
}
}
If the $request is available there, Then there is no need to pass extra $image variable.
have you tried
dd($request)
or
print_r($request->toArray()); exit;
for see what's in your request!
In your create.blade you use 'formUrl' => route('category.store'), this route calls the "store" method, right? If so, it also needs to pass the $image parameter. It would be easier to identify the problem if we could se your web routes file too.
If route('category.store') does call the store method you have a few options.
1 - If you don't really need the $image parameter for the store method, you could just remove it.
2 - If you need it in a few cases, just make the parameter optional and check if it's received before handling it. Example: store(Request $request, $image = null)
3 - If this parameter actually is required, you will have to pass it everytime, even when calling routes. Example: route('category.store', ['image' => $something]). Looking at your code at this moment in create.blade you don't have the content to pass though, so I don't think this is an option.
The problem isn't the image missing in the request object sent through the form, it is the second parameter required by the category.store method.
Even if you now send the image in the form with a hidden field, you would still need to pass it as a parameter everytime you call the category.store.
Your store method is defined like
store(Request $request, $image)
So, when you call this method, even if you're just getting the route URL with route('category.store'), you do need to send the image parameter in this call.
Example:
route('category.store', ['image' => 'image id here']);
The same goes for the route definition in your web routes file. You're using a resource route, but laravel don't expect a second parameter for the store method in a default resource, so you will need to change that.
/*
adds exception to the resource so it will not handle the store method
*/
Route::resource('admin/category', 'Admin\CategoryController')->except(['store']);
//adds a custom route that supports the $image parameter.
Route::post('admin/category/{image}', 'Admin\CategoryController#store')
Now, if you're planning to send the image through the request object, you don't need it as a second parameter, so the only thing you will need to change is to make your category.store method like that.
public function store(Request $request)

Upload Image in Vue Component with Laravel

I am making a simple website that has a feature to upload images. I tried it Laravel way which I made it in blade template and it works fine. Now I am trying to make it inside Vue Components
Here's my Create.vue
<template>
<div>
<div class="row">
<input type="hidden" name="_token" :value="csrf">
<div class="col-md-5">
<div class="detail-container">
<label for="title">Book Title:</label>
<input type="text" name="title" id="title" v-model="book_title" class="form-control">
</div>
<div class="detail-container">
<label for="title">Book Description:</label>
<textarea type="text" name="description" id="description" v-model="book_description" class="form-control" rows="5"></textarea>
</div>
<div class="detail-container">
<label for="title">Tags:</label>
<multiselect v-model="tags" :show-labels="false" name="selected_tags" :hide-selected="true" tag-placeholder="Add this as new tag" placeholder="Search or add a tag" label="name" track-by="id" :options="tagsObject" :multiple="true" :taggable="true" #tag="addTag" #input="selectTags">
<template slot="selection" slot-scope="tags"></template>
</multiselect>
</div>
</div>
<div class="col-md-7">
<!-- BOOK COVER WILL GO HERE -->
<div class="detail-container">
<label>Book Cover:</label>
<input type="file" class="form-control-file" id="book_cover" name="selected_cover" #change="onFileChange">
<small id="fileHelp" class="form-text text-muted">After you select your desired cover, it will show the preview of the photo below.</small>
<div id="preview">
<img v-if="url" :src="url" height="281" width="180" />
</div>
</div>
</div>
<div class="detail-container" style="margin-top: 20px;">
<button class="btn btn-primary" #click="saveBook()">Next</button>
</div>
</div>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
// register globally
Vue.component('multiselect', Multiselect)
export default {
// OR register locally
components: { Multiselect },
data () {
return {
csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
url: null,
selected_cover: null,
tags: [],
tagsObject: [],
selected_tags: [],
book_title: '',
book_description: ''
}
},
methods: {
getTags() {
let vm = this;
axios.get('/admin/getTags').then(function(result){
let data = result.data;
for(let i in data) {
vm.tagsObject.push({id: data[i].id, name: data[i].name});
}
});
},
addTag (newTag) {
const tag = {
name: newTag,
id: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
}
this.tagsObject.push(tag);
this.tags.push(tag);
},
selectTags(value) {
this.selected_tags = value.map(a=>a.id);
},
onFileChange(e) {
const file = e.target.files[0];
this.url = URL.createObjectURL(file);
this.selected_cover = file;
},
saveBook() {
const fd = new FormData();
fd.append('image', this.selected_cover, this.selected_cover.name)
console.log(this.selected_cover);
var book_details = {
'title': this.book_title,
'description': this.book_description,
'book_cover': this.selected_cover,
'tags': this.selected_tags
};
axios.post('/admin/saveBook', book_details).then(function(result){
console.log('done')
})
}
},
created() {
this.getTags();
}
}
</script>
<!-- New step!
Add Multiselect CSS. Can be added as a static asset or inside a component. -->
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
and here's my controller
public function store(Request $request)
{
$this->validate(request(), [
'title' => 'required|min:5',
'description' => 'required|min:10',
'book_cover' => 'required|image|mimes:jpeg,jpg,png|max:10000'
]);
// File Upload
if($request->hasFile('book_cover')) {
$fileNameWithExt = $request->file('book_cover')->getClientOriginalName();
// GET FILE NAME
$filename = pathinfo($fileNameWithExt, PATHINFO_FILENAME);
// GET EXTENSION
$extension = $request->file('book_cover')->getClientOriginalExtension();
// File Unique Name
$fileNameToStore = $filename. '_'. time().'.'.$extension;
$path = $request->file('book_cover')->storeAs('public/book_covers', $fileNameToStore);
} else {
$fileNameToStore = 'noimage.jpg';
}
$book = new Book;
$book->title = request('title');
$book->description = request('description');
$book->book_cover = $fileNameToStore;
$book->save();
$book->tags()->sync($request->tags, false);
return back()->with('success', 'Book Created Successfully!');
}
I never touched my controller because this is what I used when I do this feature in Laravel Way but when I save it, the details are being saved but the image is not uploading instead it saves noimage.jpg in the database. Does anyone know what I am doing wrong?
i tried to add this part const fd = new FormData(); in book_details but when i console.log(fd) it returned no data.
you should pass the fd object and not the book_details in your POST request.
you could do it something like this.
onFileChange(e) {
const file = e.target.files[0];
// this.url = URL.createObjectURL(file);
this.selected_cover = file;
},
saveBook() {
const fd = new FormData();
fd.append('image', this.selected_cover)
fd.append('title', this.book_title)
fd.append('description', this.book_description)
fd.append('book_cover', URL.createObjectURL(this.selected_cover))
fd.append('tags', this.selected_tags)
axios.post('/admin/saveBook', fd).then(function(result){
console.log('done')
})
}
and also, you can't just console.log the fd in the console. what you can do instead is something like this
for (var pair of fd.entries()) {
console.log(pair[0]+ ', ' + pair[1]);
}
FormData is a special type of object which is not stringifyable and cannot just be printed out using console.log. (link)

ajax form request from a while - confuse variable

I want to send a form with a button from a while, but I have a problem. If post the button, the form confuse the variable. E.g: i post the button with id 1, and the script get the variable from the last input.
Code:
index:
<?php
$res = getProdus();
foreach($res as $row) { ?>
<form action="addtocart.php" method="POST">
<div class="col-12 col-sm-6 col-md-4 single_gallery_item women wow fadeInUpBig" data-wow-delay="0.2s">
<div class="product-img">
<img src="img/product-img/product-1.jpg" alt="">
<div class="product-quicview">
<i class="ti-plus"></i>
</div>
</div>
<div class="product-description">
<h4 class="product-price">$39.90</h4>
<p>Jeans midi cocktail dress</p>
<input type="hidden" name="addtcart" value="<?=$row['ID'];?>">
<button type="submit" class="add-to-cart-btn">ADD TO CART</button>
</div>
</div>
</form>
<?php } ?>
the ajax request:
$(document).ready(function() {
$('form').submit(function(event) {
var formData = {
'addtcart' : $('input[name=addtcart]').val()
};
$.ajax({
type : 'POST',
url : 'addtocart.php',
data : formData,
dataType : 'json',
encode : true
})
.done(function(data) {
console.log(data);
});
event.preventDefault();
});
});
and the addtocart.php
<?php
include("includes/functions.php");
session_start();
$errors = array(); // array to hold validation errors
$data = array(); // array to pass back data
if (empty($_POST['addtcart']))
$errors['addtcart'] = 'Este necesar produsul id-ului.';
if ( ! empty($errors)) {
$data['success'] = false;
$data['errors'] = $errors;
} else {
$ok = AddToCart(filtrare($_POST['addtcart']), getSpec("username", "users", "email", $_SESSION['magazin-user']));
if($ok == 1) {
$data['success'] = true;
$data['message'] = 'Success!';
} else {
$data['success'] = false;
$errors['mysqli'] = "Nu s-a realizat bine query-ul.";
$data['errors'] = $errors;
}
}
echo json_encode($data);
?>
Replace your button code with this
<button type="submit" value="<?=$row['ID'];?>" class="add-to-cart-btn">ADD TO CART</button>
and after that replace you
make changes to your script code
$(".add-to-cart-btn").click(function() {
var formData = {
'addtcart' : $(this).val()
};
.
.
.
and your rest of the code.

How to refresh HTML after AJAX post

I have a site where guests can sign in via a modal form.
The solution I came up with is working but I feel like it's a dirty/insecure way to do it.
In the master layout, I load the partial view which contains the modal form.
when the user is authenticated I refresh the navbar to show the logged in username. That's the part where I'm kind of confused. Wouldn't it be possible to 'refresh' the navbar which is also a partial view.
login-modal.blade.php:
<div class="ui small modal" id="loginModal">
<div class="header">
Login
</div>
<div class="ui active dimmer" id="loader" style="display: none">
<div class="ui text loader">Loading</div>
</div>
<div class="content">
<div class="ui grid">
<div class="nine wide column centered">
{!! Form::open(array('route' => 'auth.login', 'method' => 'post','id'=>'formLogin','class' => 'ui large form')) !!}
<meta name="csrf_token" content="{{ csrf_token() }}"/>
<div class="field {!! $errors->has('password') ? 'error' : '' !!}">
<div class="ui left icon input">
<i class="user icon"></i>
{!! Form::text('username','',['name'=>'username','id'=>'username','class' => 'pd','placeholder'=>'Username']) !!}
</div>
{!! $errors->first('username', '<span class="ui text" id="" style="color: #bf4d4b">:message</span>') !!}
</div>
<div class="field {!! $errors->has('password') ? 'error' : '' !!}">
<div class="ui left icon input">
<i class="lock icon"></i>
{!! Form::password('password',['name'=>'password','id'=>'password','class' => '','placeholder'=>'Password']) !!}
</div>
{!! $errors->first('password', '<span class="ui text" id="" style="color: #bf4d4b">:message</span>') !!}
</div>
{!! Form::submit('Login',['id'=>'loginButton','class'=>'ui fluid large teal submit button']) !!}
<div class="ui error message"></div>
{!! Form::close() !!}
<div class="ui message">
No account? Sign Up
</div>
</div>
</div>
</div>
<div class="actions">
</div>
</div>
now the javascript in the same file:
<script>
$('#loginButton').click(function () {
$('#loginModal').modal(
{
blurring: true,
closable: true,
})
.modal('show');
});
$(document).ready(function () {
$('form#formLogin').submit(function (e) {
e.preventDefault();
$.ajax({
type: 'post',
timeout: 10000,
url: $('form#formLogin').attr('action'),
dataType: 'json',
data: $('form#formLogin').serialize(),
beforeSend: function (xhr) {
$('div#loader').show();
var token = $('meta[name="csrf_token"]').attr('content');
if (token) {
return xhr.setRequestHeader('X-CSRF-TOKEN', token);
}
},
complete: function () {
$('div#loader').hide();
},
success: function (data) {
if (data.success == false) {
var errors = data.errors; $('#loginModal').find('div.field.error').removeClass("field error").addClass("field");
$('#loginModal').find('span').remove();
$.each(errors, function (field, errormsg) {
if (errormsg.length != 0) {
var currentField = $('#loginModal').find('#' + field);
var currentFieldSpan = $('#loginModal').find('#span' + field);
if (currentFieldSpan.length > 0) {
$('#loginModal').find('div.field.error').removeClass("field error").addClass("field");
$('#loginModal').find('span').remove();
}
currentField.closest("div.field").removeClass("field").addClass("field error");
$("<span class='ui text' id='span" + field + "' style='color: #bf4d4b'>" + errormsg + "</span>").insertAfter(currentField.closest("div.ui.left.icon.input"));
}
});
if ((typeof data.locked != 'undefined') && data.locked.status == true) {
//BIDOUILLE pour disable le button login//
function enableLoginButton() {
$('#loginModal').find('#loginButton').removeClass('disabled');
}
//disable login button
$('#loginModal').find('#loginButton').addClass('disabled');
//after lockout time enable the login button again
setTimeout(enableLoginButton, (data.locked.remainingtime * 1000));
}
}
else if (data.success == true) {//authentication was successful
var cnt = '<div class="ui simple dropdown item">' +
'<img class="logo" src="{{ asset('images/food.png') }}" style="margin-right: 1em">' +
data.user['username'] +
' <i class="dropdown icon"></i>' +
'<div class="menu">' +
'<a class="item" href="#">Link Item</a>' +
'<a class="item" href="#">Link Item</a>' +
'<div class="divider"></div>' +
'<div class="header">Header Item</div>' +
'<div class="item">' +
'<i class="dropdown icon"></i>' +
'Sub Menu' +
'<div class="menu">' +
'<a class="item" href="#">Link Item</a>' +
'<a class="item" href="#">Link Item</a>' +
'</div>' +
'</div>' +
'<a class="item" href="#">Link Item</a>' +
'</div>' +
'</div>'
//remove the signin button
$('#navbartop .right .item').remove();
//add the dropdown with username
$('#navbartop .right').append(cnt);
//dissmis modal
$('#loginModal').modal().modal('hide');
}
},
error: function (xhr) {
var validationerrors = xhr.responseJSON;
$('#loginModal').find('div.field.error').removeClass("field error").addClass("field");
$('#loginModal').find('span').remove();
$.each(validationerrors, function (field, errormsg) {
if (errormsg.length != 0) {
//select the field
var currentField = $('#loginModal').find('#' + field);
var currentFieldSpan = $('#loginModal').find('#span' + field);
if (currentFieldSpan.length > 0) {
$('#loginModal').find('div.field.error').removeClass("field error").addClass("field");
$('#loginModal').find('span').remove();
}
//apply 'field error' class to the closest div with 'field' class
currentField.closest("div.field").removeClass("field").addClass("field error");
//appends a span with red text and the validation error message
$("<span class='ui text' id='span" + field + "' style='color: #bf4d4b'>" + errormsg + "</span>").insertAfter(currentField.closest("div.ui.left.icon.input"));
}
});
}
});
return false;
});
});
</script>
AuthController.php:
protected function handleUserWasAuthenticated(Request $request, $throttles)
{
if ($throttles) {
$this->clearLoginAttempts($request);
}
if (method_exists($this, 'authenticated')) {
return $this->authenticated($request, Auth::guard($this->getGuard())->user());
}
//if user intended to access Logout() while not logged in, avoid instant redirect and logout
if (str_contains(redirect()->intended()->getTargetUrl(),'auth/logout')) {
return redirect()->route('home.index')->with('success', Auth::user()->username.' logged in successfully. ');
}
if($request->ajax())
{
return Response::json(['success' => true, 'errors' => '','user'=> Auth::user()]);
}
return redirect()->intended($this->redirectPath())->with('success', Auth::user()->username.' logged in successfully. ');
}
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
if ($throttles && ! $lockedOut) {
$this->incrementLoginAttempts($request);
}
if($request->ajax())
{
return Response::json(['success' => false, 'errors' =>
[$this->loginUsername() => $this->getFailedLoginMessage()]
]);
}
return $this->sendFailedLoginResponse($request);
}
protected function sendLockoutResponse(Request $request)
{
$seconds = app(RateLimiter::class)->availableIn(
$this->getThrottleKey($request)
);
if($request->ajax()) {
return Response::json(['success' => false,
'errors' =>
[$this->loginUsername() => $this->getLockoutErrorMessage($seconds)],
'locked' =>
['status'=>true, 'remainingtime'=>$seconds]]);
}
return redirect()->back()
->withInput($request->only($this->loginUsername(), 'remember'))
->withErrors([
$this->loginUsername() => $this->getLockoutErrorMessage($seconds),
]);
}
I would wrap the navbar into a div like this:
<div id="ajax-nav">
<!-- Nav here -->
</div>
Then you can reload the navbar when your login response was successful:
$('#ajax-nav').load("some url that returns the html of the navbar");
Then you just need a route that leads to a Controller that can generate the navbar based on the user login state (logged in or guest).
With this procedure, you can completely replace the content of the navbar with a newly generated one after a specific event like your successful login or you could even set an interval to refresh the navbar - but that should not be needed in your case.
I guess you need to change this line:
'<img class="logo" src="{{ asset('images/food.png') }}" style="margin-right: 1em">'
For
'<img class="logo" src="{{ asset(\'images/food.png\') }}" style="margin-right: 1em">'
I hope it works ;)

Resources