File uploading in react.js and Yii2 API - javascript

I'm usin react.js(ver 15) and i want to upload the files with yii2 api.
My code is as follows:
My component(in react):
import React, { Component } from 'react';
import Header from './Heaader';
/* global $ */
class New extends Component {
constructor(props){
super(props);
this.state = {
cates: [],
subcats: [],
radioSelected: "f"
};
}
submitted = (e) => {
e.preventDefault();
var file_data = $('#filee')[0].files;
var form_data = new FormData();
form_data.append('file', file_data);
$.ajax({
url: "http://blog.dev/ads",
type: "POST",
data: form_data,
contentType: false,
cache: false,
processData:false,
success: function(data){
console.log(data);
},
error: function(){}
});
};
render(){
return (
<div>
<Header isNew="true" />
<div style={{marginTop: 2 + 'em'}}>
<form onSubmit={this.submitted}>
<input type="file" id="filee" />
<button type="submit">Sub</button>
</form>
</div>
</div>
);
}
Server Side:
In my controller:
<?php
namespace frontend\controllers;
use Yii;
use yii\rest\ActiveController;
class AdsController extends ActiveController
{
public $modelClass = 'frontend\models\Ads';
public function behaviors()
{
$behaviors = parent::behaviors();
// remove authentication filter
$auth = $behaviors['authenticator'];
unset($behaviors['authenticator']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::className(),
];
// re-add authentication filter
$behaviors['authenticator'] = $auth;
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
public function actions()
{
$actions = parent::actions();
unset($actions['create']);
return $actions;
}
public function actionCreate()
{
print_r($_FILES);
}
}
My problem is that $_FILES array is empty!
When I send data other than the file, $_POST works without problems(While this is the case in ajax request: processData:true and contentType:true ).
but $_FILES is empty.
Please guide me and tell me where the problem is?

Make sure your form has the correct encoding i.e <form enctype=multipart/form-data> for file uploads. this is most likely the reason why your $_FILES is empty
Yii 2 specifies a uniform API for fetching/saving uploads by using UploadedFile::getInstance($model, 'imageFile'); from the yii\web\UploadedFile; You should use it to access the uploads.
All the Best

Related

Multiple forms in #foreach loop. How do I submit one asynchronously with javascript. C# core Razor

Shopping cart with many items how to remove any item asynchronously with JavaScript this is my work so far. Can anyone improve on this?
your help would be greatly appreciated. Have a great day
Ok so this works if you remove items from the top of the list but fails if you remove items from some other place.
The problem seems to be that the form names are all the same "remove" without any indexing.
Problem is I'm not sure how to proceed with this.
document.forms['remove'].onsubmit = () => {
let formData = new FormData(document.forms['remove']);
fetch('/sales/cart?handler=RemoveItem', {
method: 'post',
body: new URLSearchParams(formData)
})
.then(() => {
var url = "/sales/cart?handler=CartPartial";
console.log(url)
$.ajax({
url: url,
success: function (data) {
$("#exampleModal .modal-dialog").html(data);
$("#exampleModal").modal("show");
//alert('Posted using Fetch');
}
});
});
return false;
}
<pre>
#foreach (var item in Model.Items)
{
<form name="remove" method="post">
<h4 class="text-left text-body">#item.Price.ToString("c")
<button class="btn btn-sm" title="Trash"><i style="font-size:large"
class="text-warning icon-Trash"></i></button>
</h4>
<input type="hidden" asp-for="#Model.Id" name="cartId" />
<input type="hidden" asp-for="#item.Id" name="cartItemId" />
</form>
}
</pre>
Update
----------
New markup
I added an index to the id and included an onclick event.
<form method="post" id="#i" onclick="removeItem(this.id)">
<button class="btn btn-sm" title="Trash">Item One</button>
<input type="hidden" asp-for="#Model.Id" name="cartId" />
<input type="hidden" asp-for="#item.Id" name="cartItemId" />
</form>
and create a new function that captured the form id including it in a constant.
<script>
function removeItem(formId) {
const form = document.getElementById(formId);
form.onsubmit = () => {
let formData = new FormData(form);
fetch('/sales/cart?handler=RemoveItem', {
method: 'post',
body: new URLSearchParams(formData)
})
.then(() => {
var url = "/sales/cart?handler=CartPartial";
console.log(url)
$.ajax({
url: url,
success: function (data) {
$("#exampleModal .modal-dialog").html(data);
$("#exampleModal").modal("show");
//alert('Posted using Fetch');
}
});
});
return false;
}
}
</script>
If anybody can improve on this please post it here.
Thanks.
Updates code behind Cart.cshtml.cs
using System;
using System.Threading.Tasks;
using Malawby.Models;
using Malawby.Services.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Malawby.Pages.Sales
{
public class CartModel : PageModel
{
private readonly ICartRepository _cartRepository;
public CartModel(ICartRepository cartRepository)
{
_cartRepository = cartRepository ?? throw new
ArgumentNullException(nameof(cartRepository));
}
[BindProperty]
public Cart Cart { get; set; } = new Cart();
public const string SessionKeyName = "_Name";
public string SessionInfo_Name { get; private set; }
public void OnGetAsync()
{
}
public async Task<PartialViewResult> OnGetCartPartialAsync()
{
var userName = GetUserName();
if (userName != null)
{
Cart = await _cartRepository.GetCartByUserName(userName);
}
return Partial("_ToCart", model: Cart);
}
private string GetUserName()
{
return HttpContext.Session.GetString(SessionKeyName);
}
public async Task OnPostRemoveItemAsync(int cartId, int cartItemId)
{
await _cartRepository.RemoveItem(cartId, cartItemId);
}
}
}
Update 2
This is the modified code I used. This is the error in the console.
XML Parsing Error: no root element found Location: localhost:44331/sales/cart?handler=RemoveItem Line Number 1, Column 1
There is no error on the page just nothing happens on the click of the trash can.
<script type="text/javascript">
function removeItem(cartItemId, cardId) {
var removeUrl = "/sales/cart?handler=RemoveItem";
$.post(removeUrl,
{
cartItemId: cartItemId,
cardId: cardId
})
.done(function (data) {
alert(data); //usually return true or false if true
remove card
$('#card_' + cardId).remove();
});
}
</script>
I am not familiar with asp.net core, but I will show how I usually do it without focusing on syntax.
first on the view no need to add multiple form but should use card id as index and delete button sent selected index like this:
#foreach (var item in Model.Items)
{
<div id="card_#item.cardId">
<h4 class="text-left text-body">#item.Price.ToString("c")
<button class="btn btn-sm" onclick="removeItem('#item.cardId') title="Trash"><i style="font-size:large"
class="text-warning icon-Trash"></i></button>
</h4>
</div>
}
then the script function will call remove api and remove selected card with no need to re-render the page:
<script type="text/javascript">
function removeItem(cardId) {
var removeUrl = "your apiUrl";
$.post( "removeUrl", { cardId: cardId })
.done(function( data ) {
alert( data ); //usually return true or false if true remove card
$('#card_'+ cardId).remove();
});
}
</script>

How to make a post request to a protected Laravel API route with JavaScript using the API token?

Description
I have a table, where i collect values from checkboxes with JavaScript. This values should be send to a protected API route in a Laravel backend.
I use the standard Laravel auth setup (out of the box).
Question
What do I have to send with the JavaScript post request for authentication and how do i do that? Can i add a auth token or something like that to the headers?
At the moment i get the reponse:
"This action is unauthorized".
exception: "Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException"
Edit
At the current point of my research the api token seems to be a simple solution for my case. But i can't figure out how to attach the api token to the JavaScript post request.
Thats the JavaScript function for collecting the values storing them in objects.
import SaveData from "../api/SaveData";
export default async function SaveMultipleReports() {
const table = document.getElementById("reports-dashboard");
const rows = table.querySelectorAll("div[class=report-tr]");
let reports = [];
for (const row of rows) {
const checkbox_visible = row.querySelector("input[name=visible]")
.checked;
const checkbox_slider = document.querySelector(
"input[name=show_in_slider]"
).checked;
const report = {
id: row.id,
visible: checkbox_visible,
show_in_slider: checkbox_slider
};
reports.push(report);
}
console.log(reports);
const response = await SaveData("/api/reports/update", reports);
console.log(response);
}
And that is the SavaData function:
export default async function SaveData(api, data) {
const token = document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content");
const url = window.location.origin + api;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": token,
Accept: "application/json"
},
body: JSON.stringify(data)
});
const result = await response.json();
return result;
}
And thats the line in the api.php:
Route::middleware("can:administration")->post("reports/update", "ReportsController#UpdateAll");
The whole repo is here.
Thanks for your time in advance :)
Edit 2
For now i managed it without JavaScript. Put all the values, i want to update in form and load a hidden input for the ID of every object (the ID is needed for the controller afterwards).
Thanks to this post.
{!! Form::open(["route" => ["admin.reports.multiupdate"], "method" => "PUT", "class" => "report-table"]) !!}
... // some HTML
#foreach ($reports as $report)
<div class="report-tr">
<input type="hidden" name="reports[{{$loop->index}}][id]" value="{{$report->id}}">
<div class="td-name">
<p class="td-text">{{$report->name}}</p>
</div>
<div class="td-flex">{{$report->body}}</div>
<div class="tr-wrapper">
<div class="checkbox-visible">
<div class="checkbox-container">
<input class="checkbox" type="checkbox" name="reports[{{$loop->index}}][visible]" value="1" checked>
<span class="checkmark"></span>
</div>
<label class="table-label" for="visible">Sichtbar</label>
</div>
<div class="checkbox-slider">
<div class="checkbox-container">
<input class="checkbox" type="checkbox" name="reports[{{$loop->index}}][show_in_slider]" value="1"
{{($report->show_in_slider == 1 ? "checked" : "")}}>
<span class="checkmark"></span>
</div>
<label class="table-label" for="show_in_slider">Im Slider</label>
</div>
<div class="td-buttons">
...
#endforeach
<button class="floating-save">
#svg("saveAll", "saveAll")
</button>
{!! Form::close() !!}
And a snippet from the Controller:
public function MultipleUpate(ReportUpdate $request)
{
$reports = $request->input("reports");
foreach ($reports as $row) {
$report = Report::find($row["id"]);
// giving the checkbox 0, if it isn't checked
$isVisible = isset($row["visible"]) ? 1 : 0;
$inSlider = isset($row["show_in_slider"]) ? 1 : 0;
$report->visible = $isVisible;
$report->show_in_slider = $inSlider;
$report->new = false;
if ($report->save()) {
$saved = true;
}
}
if ($saved == true) {
$request->session()->flash("success", "Änderungen gespeichert!");
} else {
$request->session()->flash("error", "Das hat nicht geklappt!");
}
return back();
The ReportUdpate function contains only that:
public function authorize()
{
return true;
}
public function rules()
{
return [
"visible" => "nullable",
"show_in_slider" => "nullable"
];
}
You are talking about authentication but using an authorization middleware. There is a difference between the two.
Read about it here: https://medium.com/datadriveninvestor/authentication-vs-authorization-716fea914d55
With that being said, what you are looking for is an authentication middleware that protects your routes from unauthenticated users. Laravel provides a middleware called Authenticate out of the box for this specific purpose.
Change your route to be like so:
Route::middleware("auth")->post("reports/update", "ReportsController#UpdateAll");

Email multiple images with dropzone laravel

I want to upload multiple image files and send it via email to customers. But the ajax request get this error call to a member function getclientoriginalname() on array when uploadMultiple: true, added to dropzone. Multiple images uploaded without that option. Anyway i want to email that multiple files, how can i do this?
dropzone js code:
Dropzone.options.uploadimg = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 5, //MB
acceptedFiles: ".jpeg,.jpg,.png",
uploadMultiple: true,
addRemoveLinks: true,
success: function(file, response)
{
$.notify({
message: 'Image uploaded Successfully!'
},
{
type: 'success'
});
},
error: function(file, response)
{
return false;
console.log('fail to upload');
},
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
}
SendMailController to send uploaded images.
public function sendNotifications(Request $request)
{
$id_int = Cookie::get('jobid');
$img_name = Cookie::get('imgName');
$data = DB::table('customers')
->join('jobs', 'jobs.id', '=', 'customers.id')
->select('firstname','email')
->where('jobs.id', '=', $id_int)
->get()->toArray();
foreach ($data as $value) {
$customer_firstname = $value->firstname;
$customer_email = $value->email;
}
$pathToFile = public_path() . "\\uploads\\" . $img_name;
//send the email to the relevant customer email
Mail::to($customer_email)->send(new SendMail($customer_firstname, $pathToFile), function($message){
$message->attach($pathToFile);
});
}
Image upload controller:
class ImageUploadController extends Controller
{
public function uploadImage(Request $request){
$img_file = $request->file('file');
$imgName = $img_file->getClientOriginalName();
Cookie::queue(cookie('imgName', $imgName, $minute = 5));
$img_file->move(public_path('uploads'), $imgName);
}
}
When i uploaded multiple images and sendinding via email, it only send the last uploaded file in dropzone. How can i send all uploaded files?
since its multiple files you need to loop through the file variable to get the files
class ImageUploadController extends Controller
{
public function uploadImage(Request $request){
$img_files = $request->file('file');
foreach($img_files as $img_file){
$imgName = $img_file->getClientOriginalName();
Cookie::queue(cookie('imgName', $imgName, $minute = 5));
$img_file->move(public_path('uploads'), $imgName);
}
}
}

Get the article's view times using Vue.js and Laravel 5.3

My thought process:
When the show page opens, get the article's id with JavaScript.
Check this id exist or not in cookie
If not exists, write it into cookie and send an ajax request, the backend updates view times.
If exists, do nothing.
Demo:
View:
<div class="card">
<div class="card-block text-xs-center">
<h5 class="card-title">{{$article->title}}</h5>
<hr class="m-y-2">
<h6 class="card-subtitle text-muted">date:{{$article->created_at->format('Y-m-d')}}
    views:{{$article->view_times}}</h6>
</div>
<div class="card-block">
<p class="card-text">{{$article->content}}</p>
</div>
</div>
Controller:
class ArticlesController extends Controller
{
//`show` method
public function show($id)
{
$article = Article::findOrFail($id);
return view('show', compact('article'));
}
//the method of updating view times.
public function statistics(Request $request)
{
$id = $request->input('id');
$article = Article::findOrFail($id);
$view_time=$article->view_time;
$article->view_time=$view_time+1;
$article->save();
}
}
JavaScript:
Vue.http.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name=csrf-token]').getAttribute('content')
Vue.http.options.emulateJSON = true;
var vm = new Vue({
el: "body",
data: function(){
return{
id:[]
}
},
created() {
//1、Get the article's id.Do I have to send an ajax request? Is there any other way?
this.$http.get('article/get-id').then((response) => {
// success callback
this.id=response.data;
}, (response) => {
// error callback
});
//2、After Getting the `id`,check it in cookie,I don't know how to do it?
//3、If not exists,write it into cookie and send an ajax request,how to write the if() sentence?
if(){
var formData = new FormData();
var id=this.id;
formData.append('id',id);
this.$http.patch('article/statistics', formData,{
before(request) {
if (this.previousRequest) {
this.previousRequest.abort();
}
this.previousRequest = request;
}
}).then((response) => {
// success callback
}, (response) => {
// error callback
});
}
}
});
Questions:
There are three questions, shown as comments in JavaScript code above.

PHP session not working in Aurelia

I'm using Aurelia with PHP as backend. Here is my view with its model:
home.html
<template>
<require from="datepicker.js"></require>
<form submit.delegate="submit()">
<input value.bind="name"></input>
<input value.bind="age"></input>
<button type="submit" name="submit">Submit
</button>
</form>
</template>
home.js
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import 'fetch';
#inject(HttpClient)
export class Home {
name;
age;
constructor(http) {
this.http = http;
}
submit() {
this.jsonobj = {
'name': this.name,
'age': this.age
};
if (this.jsonobj.name && this.jsonobj.age) {
this.http.fetch('dist/components/ses.php', {
method: 'post',
body: JSON.stringify(this.jsonobj)
})
.then(response => response.json())
.then(data =>
{
console.log(data);
});
}
}
}
And here is the PHP script:
ses.php
<?php
session_start();
$word = 'lol';
if(!isset($_SESSION['username'])){
$_SESSION['username'] = 'kil';
}
$input = file_get_contents('php://input');
$input_json_array = json_decode($input);
echo json_encode(array('item' => $_SESSION['username']));
?>
I expect that after the first call to the script, $_SESSION['username'] will be set to 'kil'. So on next ajax post !isset($_SESSION['username'] wont evaluate to true but it does which means PHP session isn't working.
By default fetch (the web standard that aurelia-fetch-client is built on) does not send cookies. You'll need to use credentials: 'include' in your request init object.
Two great resources for fetch:
https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch
https://davidwalsh.name/fetch
The codez:
this.http.fetch('dist/components/ses.php', {
credentials: 'include', // <-------------------------------------
method: 'post',
body: JSON.stringify(this.jsonobj)
})

Categories