Array of files cann't be validated by the server(php) - javascript

I am trying to pass below data to the laravel backend:
form: new Form( {
name: '',
file2: null,
licenses: [
{name: '', xerox: null},
{name: '', xerox: null},
]
}),
I am facing laravel validation problem. Like, if I tried to send data with the help of FormData() then php received the array and can validate the data except array element (i.e. Licenses) which also contains the attached files in an array of objects.
On backend side, it receives like below image. It doesn’t shows attached licenses images, except the profile image.
Below are the codes details also includes the repo of the project, if it needed.
Could you please tell me how could I sent the data with the attachment to the server?
Example-component.vue
<template>
<b-form #submit.prevent="onSubmit" #keydown="form.errors.clear($event.target.name)">
<b-form-group id="input-group-2" label="Your Name:" label-for="input-2">
<b-form-input
id="input-2"
v-model="form.name"
placeholder="Enter name"
></b-form-input>
</b-form-group>
<input type="file" id="file" ref="file" v-on:change="handleFileUpload()" class="mb-3"/>
<!--License-->
<b-form-group label="License details(if applicable):" class="">
<b-form-group v-for="(l, index) in form.licenses" :key="l.index" align-v="center" class="">
<b-card bg-variant="light">
<b-form-group>
<b-form-input id="input-license1" v-model="l.name" placeholder="Enter your License name:" class=""></b-form-input>
</b-form-group>
<b-form-group>
<input
type="file"
id="filelicense"
name="xerox"
ref="licenseFile"
v-on:change="handleLicenseFileUpload($event.target.name, $event.target.files[0], index)"
class="mb-3"/>
<div>Selected file: {{ l.xerox ? l.xerox.name : '' }}</div>
</b-form-group>
</b-card>
</b-form-group>
</b-form-group>
<b-button type="submit" variant="primary">Submit</b-button>
</b-form>
</template>
<script>
import Form from "../core/Form";
export default {
data() {
return {
form: new Form( {
name: '',
file2: null,
licenses: [
{name: '', xerox: null},
{name: '', xerox: null},
]
}),
isLoading: false
}
},
methods: {
handleLicenseFileUpload(fieldName, SelectedFile, index) {
console.log(SelectedFile);
this.form.licenses[index].xerox = SelectedFile;
},
onSubmit() {
this.isLoading = true;
this.form.post('/candidates')
.then(response => {
this.response = response
console.log(response.data.message)
})
.catch((err) => {
})
.finally(() => {
this.isLoading = false
})
},
handleFileUpload(){
this.form.file2 = this.$refs.file.files[0];
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
CandidateController.php
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required',
'file2' => '',
'licenses.*.name' => 'required',
'licenses.*.xerox' => 'required',
]);
dd($request->all());
Form.js
courtesy
import Errors from './Errors';
class Form {
constructor(data) {
this.originalData = data;
for (let field in data) {
this[field] = data[field];
}
this.errors = new Errors();
}
data() {
let data = {};
for (let property in this.originalData) {
data[property] = this[property];
}
return data;
}
setFormData(data) {
let formData = new FormData();
for (let field in data) {
formData.append(field, data[field]);
}
return formData;
}
reset() {
for (let field in this.originalData) {
this[field] = '';
}
this.errors.clear();
}
post(url) {
return this.submit('post', url);
}
submit(requestType, url) {
let config = {
headers: {
Authorization: 'sometoken',
'Content-Type': `multipart/form-data; boundary=${Math.random().toString().substr(2)}`,
}
}
return new Promise((resolve, reject) => {
axios[requestType](url, this.setFormData(this.data()))
.then(response => {
this.onSuccess(response.data);
resolve(response.data);
})
.catch(error => {
this.onFail(error.response.data.errors);
reject(error.response.data);
});
});
}
onSuccess(data) {
//alert(data.message); // temporary
this.reset();
}
onFail(errors) {
this.errors.record(errors);
}
}
export default Form;

Related

Laravel: Redirect with flash message after submitted form using vue.js

I have two components: Index and Create. Both of these components are loaded from different blade files. The problem is I cannot pass flash message as a prop since these are in different files. How to redirect after a submitted form has been created and and receive the flash message in component Index from Create?
public function store(Request $request)
{
Service::create([
'name' => $request->get('name'),
'vendor_id' => $request->get('vendor'),
'desc' => $request->get('desc'),
'hours' => $request->get('hours'),
'price_per_hour' => $request->get('price'),
'fixed_price' => $request->get('fixed_price'),
'user_id' => $request->user()->id
]);
if (\request()->wantsJson()) {
return response()->json([
'alert_store' => 'New service added successfully.'
]);
}
}
Create component:
if(ok) {
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/api/services', {
name: this.form.name,
vendor: this.form.vendor,
desc: this.form.desc,
hours: this.form.hours,
price: this.form.price,
fixed_price: this.form.fixed_price,
})
.then(response => {
this.alert_store = response.data.alert_store
alert(this.alert_store)
window.location.href = '/admin/services';
})
.catch(function (error) {
console.log(error);
});
});
}
},
index.vue:
import Create from './Create.vue';
import Alert from '../Alert.vue';
export default {
props: ['alert_store'],
components: {
Create,
Alert,
},
data(){
return {
services: [],
alert_success: '',
alert_error: '',
errors: [],
success: [],
form: {
name: '',
desc: '',
hours: '',
price_per_hour: '',
vendor: '',
fixed_price: '',
},
selected: null,
}
},
mounted() {
this.loadServices()
this.getStatus()
},
methods: {
loadServices: function(){
axios.get('/api/getservicedata')
.then(response => {
this.services = response.data;
})
.catch(function(error){
console.log(error);
});
},
// getStatus(){
// axios
// .post('/api/services')
// .then(response => {
// console.log(response.data.alert_store)
// });
// },
isEditable(service)
{
this.form.name = service.name
this.form.desc = service.desc
this.form.hours = service.hours
this.form.price_per_hour = service.price_per_hour
this.form.vendor = service.vendor.id
this.form.fixed_price = service.fixed_price
if(service.isEditing)
{
this.selected = service.id
service.isEditable = false
}
this.$set(service, 'isEditing', true)
},
editService(service)
{
if(confirm('Are you sure?'))
{
axios.post(`/api/services/${service.id}`, {
_method: 'patch',
name: this.form.name,
vendor: this.form.vendor,
desc: this.form.desc,
hours: this.form.hours,
price_per_hour: this.form.price_per_hour,
vendor: this.form.vendor,
fixed_price: this.form.fixed_price
})
.then(response => {
this.alert_success = response.data.alert_update
this.success.push(Alert)
this.loadServices()
})
.catch(response => {
this.errors.push(Alert)
this.alert_error = `Could not edit, ${service.name}, from services!`
})
}
},
deleteService(service)
{
if(confirm('Are you sure?'))
{
axios.get('/sanctum/csrf-cookie').then(response => {
axios.delete(`/api/services/${service.id}`, {
__method: 'DELETE'
})
.then(response => {
this.alert_success = response.data.alert_delete
this.success.push(Alert)
this.loadServices()
})
.catch(response => {
this.errors.push(Alert)
this.alert_error = `Could not delete, ${service.name}, from services!`
})
});
}
}
}
}
You could pass props into index.vue component with different ways.
First and properly (on my mind) way is to use session data.
Like:
public function store(Request $request)
{
Service::create([
'name' => $request->get('name'),
'vendor_id' => $request->get('vendor'),
'desc' => $request->get('desc'),
'hours' => $request->get('hours'),
'price_per_hour' => $request->get('price'),
'fixed_price' => $request->get('fixed_price'),
'user_id' => $request->user()->id
]);
\request()->session()->put('yourawesomekey','value');
if (\request()->wantsJson()) {
return response()->json([
'alert_store' => 'New service added successfully.'
]);
}
}
And when you initialize your component in blade file:
...
<your-component-name alert_store="{{ session('yourawesomekey', 'default-value') }}" />
...
NOTE Don't forgot to cleanup session data \request()->session()->forget('yourawesomekey');
Hope it will work for you.

Vuetify form .$refs validate is not a function

Im getting Error in v-on handler: "TypeError: this.$refs.EmailMessage.validate is not a function on my form when I click on send in console as well as this.$refs.EmailMessage.validate is not a function.
I created a Mapactions where I commit the Payload to the emailjs server
I've tested $refs somewhere else and it does the same thing. could it be that Vuejs has a bug? or am I doing something silly?
My form in my Contact page
<v-form ref="EmailMessage" v-model="valid" lazy-validation>
<v-text-field
solo
:rules="[required]"
v-model="fd.name"
label=" Name & Surname"
name="nameSurname"
type="text"
required
></v-text-field>
<v-text-field
solo
:rules="emailRules"
v-model="fd.email"
label="E-mail address"
name="emailAddress"
required
></v-text-field>
<v-textarea
solo
v-model="fd.Message"
:rules="[required]"
label="Message"
name="Message"
required
></v-textarea>
<p class="text-right red--text ma-0 py-3" v-if="emailError">
{{ emailError }}
</p>
<v-btn
color="#212529"
dark
#click="validate()"
:loading="loading"
:disabled="!valid"
block
>SEND</v-btn
>
</v-form>
My method handling the send and reset of my contact form
<script>
import { mapState } from "vuex";
import { mapActions } from "vuex";
import emailjs from "emailjs-com";
export default {
data: () => ({
emailError: null,
valid: true,
loading: false,
required: (v) => !!v || "This field is required",
emailRules: [
(v) => !!v || "E-mail is required",
(v) => /.+#.+\..+/.test(v) || "E-mail must be valid",
],
///////////
fd: {
name: process.env.NODE_ENV == "development" ? "Test name" : null,
email: process.env.NODE_ENV == "development" ? "email#gmail.com" : null,
Message: process.env.NODE_ENV == "development" ? "Hello World" : null,
},
}),
methods: {
...mapActions(["sendMail"]),
validate() {
if (this.$refs[`EmailMessage`].validate()) {
this.loading = true;
emailjs
.send(
"gmail_service_id",
"ContactForm",
this.fd,
"userIDhere"
)
.then((result) => {
console.log("SUCCESS!", result.status, result.text);
this.loading = false;
this.resetForm();
})
.catch((e) => {
console.log("Error", e);
this.loading = false;
this.emailError = "Error while trying to send email";
});
}
},
resetForm() {
this.$refs[`EmailMessage`].reset();
},
contactImage: function (path) {
return require("#/" + path);
},
},
computed: {
...mapState("staticData", ["contact", "contactSocialMedia"]),
},
};
</script>
my actions in my store index.js
actions: {
sendMail: ({
commit
}, pl) => new Promise((resolve, reject) => {
if (pl.name) {
console.log('PL recieved: ', pl)
resolve('email is sent')
} else {
reject('email is not sent')
}
}),
},
I would really appreciate some help.
Got it to work!
I had a look at this example and gave it a try this.$refs[(“p” + index)].focus is not a function
problem was you need to add an array of 0 to the line where $refs are.
here are my methods under export default
methods: {
...mapActions(["sendMail"]),
validate() {
//Added [0] after email message
if (this.$refs[`EmailMessage`][0].validate()) {
this.loading = true;
emailjs
.send(
"gmail_service_id",
"ContactForm",
this.fd,
"InsertemailjsserviceIDhere"
)
.then((result) => {
console.log("SUCCESS!", result.status, result.text);
this.loading = false;
this.resetForm();
})
.catch((e) => {
console.log("Error", e);
this.loading = false;
this.emailError = "Error while trying to send email";
});
}
},
resetForm() {
//Added [0] after email message
this.$refs[`EmailMessage`][0].reset();
},
},

How to pass data from vue component to view(HTML) in laravel

I'm trying to pass data from vue component to blade file. I try to create props but it's didn't work for me. Is it any possible to pass the object to props to get the data? I'm new laravel. I want to pass data which subject, message, days, condition and module name to blade file(view).
I kept searching for this but couldn't find an answer that will make this clear.
Thanks!
blade.php
<div id="app">
<email-component
email_edit_route="{{ route('havence.automail.edit',['id'=>$mailTemplates->id]) }}"
>
</email-component>
</div>
Vue.js
<script>
import Vue from 'vue'
import axios from 'axios'
import MarkdownIt from 'markdown-it'
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
var msg_editor;
const md = new MarkdownIt({
linkify: true
})
export default {
props: ['email_creation_link', 'email_index_route', 'email_edit_route','conditions','modules'],
components: {
},
data() {
return {
template: {
subject: '',
message: '' ,
days: '',
condition_id: 1,
},
options:[
{
display:'Client Name',
actual:'Client name'
},
{
display:'Joined Date',
actual:'Joined date'
},
{
display:'Module Name',
actual:'Module name'
},
{
display:'Last Seen',
actual:'Last seen'
},
],
showName: false,
}
},
mounted(){
var self = this;
ClassicEditor
.create(document.querySelector( "#msg"),
{
})
.then(editor => {
msg_editor = editor;
editor.model.document.on( 'change:data', () => {
self.template.message = msg_editor.getData();
});
})
.catch(error => {
console.error(error);
})
},
methods: {
//Drag items
dragstart: function(item, e){
this.draggingItem = item;
e.dataTransfer.setData('text/plain', item.actual);
},
dragend: function(item,e) {
e.target.style.opacity = 1;
},
dragenter: function(item, e) {
this.draggingItem = item;
},
//content
replaceVariables(input)
{
let updated = input
return updated
},
//hidecontent
showHide: function(e)
{
console.log("Show "+e.target.value+ " fields")
this.showName = e.target.value !== ''
},
fetch()
{
//request data
axios.get(this.email_index_route,this.template)
.then((res) => {
this.template = res.data.template;
})
},
save()
{
//save data to db
axios.post(this.email_index_route, this.templates)
.then((res) => {
alert('Mail sent successfull!')
})
},
addToMail: function(type, text)
{
if (type == 'message') {
this.template.message += text;
msg_editor.setData(this.template.message);
}
},
//user name replace
replaceVariables() {
return this.replaceVariables(this.options || '')
},
}
}
</script>
Quick solution. : why not store data in local storage and fetch it from laravel blade ?
Next solution: you can fire global event from vue and listen on laravel blade .
why dont you send the data through ajax post call and get the data from the controller ?
then pass the data object to blade template.

Remove class for another user vue.js

I have chat message system.
I have code:
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'seen',
],
computed: {
className() {
return this.seen;
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
App.js:
data:{
message: '',
convId: 1,
chat: {
message: [],
user: [],
time: [],
seen: [],
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send(){
if(this.message.length != 0 && this.message.length <= 4000) {
this.chat.message.push(this.message);
this.chat.user.push('you');
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then( response => { this.chat.seen.push(''); //remove unread class })
.catch( response => { console.log(response) } )
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.message.push(e.message);
this.chat.user.push(e.user);
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
console.log(e);
})
.listenForWhisper('typing', (e) => {
if(e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
}
My chat.blade.php:
<message v-for="value,index in chat.message"
:key=value.index
:user=chat.user[index]
:message="chat.message[index]"
:time="chat.time[index]"
:seen="chat.seen[index]"
>
</message>
<div class="form-group">
<textarea maxlength="4000" cols="80" rows="3" class="message-input form-control" v-model='message' v-on:click="seenMessage"></textarea>
</div>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
My function seen:
public function setMessagesSeen(Conversation $conversation) {
$user = User::find(Auth::id());
$conversations = Chat::conversation($conversation->id);
//$dd = Chat::conversations($conversation)->for($user)->readAll();
dd(Chat::conversations($conversations)->for($user)->getMessages()->where('body', 'asdfsadfsd'));
//$message = Chat::messages($message)->for($user)->markRead();
broadcast(new HasSeenMessage($message));
return response('ok');
}
How I can send class "unread" to element div other user? I can paste class on current user, and I get color on element chat only for me, but how I can hide element for me and other user, when message is seen?
I want do read/unread function for users.
Example:
If user in real time send message I send class unread, when other user click on textarea, I remove class unread, and said user, that message is seen. How I can do it in real time add/remove class unread? My function is not working.
To do this you have to create an Event in your Laravel application that you will broadcast on a precise channel (you can for example give the name 'chat. {Conversation}. {User_id}') and with Laravel Echo you will listen this event!
I allowed myself to make some changes in your code -:)
I presume you have this class HasSeenEvent
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class HasSeenEvent implements ShouldBroadcast
{
use SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #param Message $message
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
public function broadcastOn()
{
// I presume we can get conversation id like this : $this->message->conversation->id
return new PrivateChannel('chat.'.$this->message->conversation->id.'.'.$this->message->sender->id);
}
}
Then, in your routes/broadcast.php declare this route chat.{conversation}.{user_id}
In the function where you put the 'seen' to 1 you broadcast the event at the same time
broadcast(new HasSeenMessage($message))
Then you listen to this event in your vuejs code
components/Message.js
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'readed',
],
computed: {
className() {
return this.readed == 1 ? 'seen' : 'unread';
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
chat.blade.php
<message v-for="message,index in chat.messages"
:key="index"
:user="message.user"
:message="message.content"
:time="message.time"
:readed="message.readed"
>
</message>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
App.js
data: {
message: '',
convId: 1,
chat: {
messages: [],
/* message: [],
user: [],
time: [],
seen: [], */
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send() {
if (this.message.length != 0 && this.message.length <= 4000) {
let data = {
content: this.message,
user: 'you',
time:this.getTime(),
readed: 0
}
this.chat.messages.push(data)
data = {}
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then(response => {
//This is not the best way to do that
this.chat.messages[this.messages.length -1 ].readed = 0
}).catch(response => {
console.log(response)
})
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.messages.push({
content: e.message,
user: e.user,
time: this.getTime(),
readed: 0
})
console.log(e);
})
.listenForWhisper('typing', (e) => {
if (e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
// I presume to can access to user info
let that = this
Echo.private('chat.'+convId+'.'+user.id)
.listen('HasSeenMessage', (e) => {
let message = e.message
let lookingForMessage = that.chat.messages.find((m) => {
// I presume in your db messages table has field content and time
return m.content == message.content && m.time => message.time
})
try {
lookingForMessage.readed = 1
}catch (err){
// message not found
console.log(err)
}
})
}
Hope it helped you!

Vue js: Vuetify server side Datatable search filter not working

I'm using vuetify for my datatable. Pagination and sort are working except the search filter. The response data from search filter is correct but the problem is its not rendering the response to my template. In vuetify docs theres only have for pagination and sort. I'm trying to implement the search function via server-side.
My User.vue
export default{
data () {
return {
max25chars: (v) => v.length <= 25 || 'Input too long!',
tmp: '',
search: '',
totalItems: 0,
pagination: {
rowsPerPage: 1,
search: ''
},
headers: [
{
text: 'Name',
sortable: true,
value: 'name',
align: 'left'
},
{
text: 'Email Add',
sortable: true,
value:'email',
align: 'left'
},
{
text: 'Roles',
sortable: true,
value:'roles_permissions',
align: 'left'
},
{
text: 'Date joined',
sortable: true,
value:'created_at',
align: 'left'
}
],
items: [],
loading: false,
timer: null
}
},
watch:{
pagination:{
handler(){
this.getDataFromApi()
.then(data => {
const self = this;
self.items = data.items;
self.totalItems = data.total;
})
},
deep: true
}
},
mounted(){
this.getDataFromApi()
.then(data => {
this.items = data.items;
this.totalItems = data.total;
});
},
methods:{
getDataFromApi(search_val){
this.loading = true;
return new Promise((resolve, reject) => {
const { sortBy, descending, page, rowsPerPage } = this.pagination
const search = this.search;
//console.log(search);
clearTimeout(this.timer);
this.timer = setTimeout(function(){
axios({
url: '/prod/api/user_table',
method:'post',
data:{
sortBy : sortBy,
descending: descending,
page : page,
rowsPerPage : rowsPerPage,
search_val : search
}
})
.then(response=>{
if(response.status == 200){
let items = response.data.data;
const total = response.data.totalRecords;
this.loading = false;
resolve({
items,
total
});
}
})
.catch(error=>{
if(error.response){
console.log(error.response);
}
})
},1000);
})
},
fetchDataFromApi(value){
//console.log(value);
}
},
created(){
}
}
Here is my back end side using laravel
public function dataTable(Request $request){
//return Datatable::eloquent(User::query())->make(true);
$sortBy = $request->sortBy;
$descending = $request->descending;
$page = $request->page;
$rowsPerPage = $request->rowsPerPage;
$search_val = $request->search_val;
//echo $rowsPerPage;
if($descending){
$orderedBy = 'desc';
}else{
$orderedBy = 'asc';
}
$start = ($page - 1) * $rowsPerPage;
/*$totalRec = User::all();
if(empty(trim($search_val))){
$user = User::orderBy($sortBy,$orderedBy)->skip($start)->take($rowsPerPage)->get();
}else{
$user = User::where([
]);
}*/
$query = User::query();
$column = ['name', 'email'];
foreach ($column as $col) {
$query->orWhere($col, 'LIKE','%'.$search_val.'%');
}
$query->orderBy($sortBy,$orderedBy)->skip($start)->take($rowsPerPage);
$arr_items = [];
foreach ($query->get()->toArray() as $shit => $v) {
$arr_items['data'][] = array(
'value' => $v['id'],
'name' => $v['name'],
'email' => $v['email'],
'roles_permissions' => '',
'created_at' => $v['created_at']
);
}
$arr_items['totalRecords'] = User::count();
return response()->json($arr_items);
}
server side search & sort of datatable in vuetify.js
If we need server side search and sort in vuetify.js datatable, we have to make some changes in vuejs part.
import {environment} from '../../environment';
export default {
name: "Category",
data() {
return {
categories: [],
search: '',
total: 0,
loading: false,
pagination: {},
headers: [
{text: 'ID', value: 'id'},
{text: 'Name', value: 'name'},
{text: 'Actions', value: 'name', sortable: false, align: 'center'}
],
rowsPerPageItems: [5, 10, 20, 50, 100],
}
},
watch: {
pagination {
this.getCategoriesByPagination();
},
search() {
this.getCategoriesByPagination();
}
},
methods: {
getCategoriesByPagination() {
this.loading = true;
// get by search keyword
if (this.search) {
axios.get(`${environment.apiUrl}/category-filter?query=${this.search}&page=${this.pagination.page}&per_page=${this.pagination.rowsPerPage}`)
.then(res => {
this.categories = res.data.data;
this.total = res.data.meta.total;
})
.catch(err => console.log(err.response.data))
.finally(() => this.loading = false);
}
// get by sort option
if (this.pagination.sortBy && !this.search) {
const direction = this.pagination.descending ? 'desc' : 'asc';
axios.get(`${environment.apiUrl}/category-order?direction=${direction}&sortBy=${this.pagination.sortBy}&page=${this.pagination.page}&per_page=${this.pagination.rowsPerPage}`)
.then(res => {
this.loading = false;
this.categories = res.data.data;
this.total = res.data.meta.total;
});
} if(!this.search && !this.pagination.sortBy) {
axios.get(`${environment.apiUrl}/category?page=${this.pagination.page}&per_page=${this.pagination.rowsPerPage}`)
.then(res => {
this.categories = res.data.data;
this.total = res.data.meta.total;
})
.catch(err => console.log(err.response.data))
.finally(() => this.loading = false);
}
}
}
}
in html part
<v-text-field v-model="search"
append-icon="search"
label="Search"
single-line
hide-details
></v-text-field>
<v-data-table :headers="headers"
:items="categories"
:pagination.sync="pagination"
:total-items="total"
:rows-per-page-items="rowsPerPageItems"
:loading="loading"
></v-data-table>
in Laravel part, i used laravel scout package.
Controller
/**
* Get category
* #return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function getAll()
{
$per_page = empty(request('per_page')) ? 10 : (int)request('per_page');
$categories = Category::latest()->paginate($per_page);
return CategoryResource::collection($categories);
}
/**
* Get category by search results
* #return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function getBySearch()
{
$per_page = empty(request('per_page')) ? 10 : (int)request('per_page');
$categories = Category::search(request()->query('query'))->paginate($per_page);
return CategoryResource::collection($categories);
}
/**
* Get category by sorting
* #return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function getByOrder()
{
$per_page = empty(request('per_page')) ? 10 : (int)request('per_page');
$direction = request()->query('direction');
$sortBy = request()->query('sortBy');
$categories = Category::orderBy($sortBy, $direction)->paginate($per_page);
return CategoryResource::collection($categories);
}
Route
Route::get('category', 'Api\CategoryController#getAll');
Route::get('category-filter', 'Api\CategoryController#getBySearch');
Route::get('category-order', 'Api\CategoryController#getByOrder');
Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Category extends Model
{
use Searchable;
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
return [
'name' => $this->name
];
}
}
To enable server-side search to work don't pass the search prop to v-data-table. Otherwise the datatable pagination and search are client side even if you pass the "totalItems" prop.
You can pass the search prop but the initial value needs to be null. I tried it first with an empty string and it didn't work, at least in my case.
<template>
<div class="data-table">
<v-data-table :headers="headers" :items="desserts" :items-per-page="5" :options.sync="options" :server-items-length="totalDesserts" :loading="loading" class="elevation-1" ></v-data-table>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import axios from 'axios'
import { api } from '~/config'
import Form from '~/mixins/form'
export default {
data: () => ({
desserts_s: [],
totalDesserts: 0,
loading: true,
options: {},
headers: [
{ text: 'id', value: 'id' },
{ text: 'lastname', value: 'lastname' },
{ text: 'email', value: 'email' },
],
desserts: [],
}),
watch: {
options: {
handler () {
this.getDataFromApi()
.then(data => {
this.desserts = data.items
this.totalDesserts = data.total
})
},
deep: true,
},
},
mounted () {
this.getDataFromApi()
.then(data => {
this.desserts = data.items
this.totalDesserts = data.total
})
},
methods: {
getDataFromApi () {
this.loading = true
return new Promise((resolve, reject) => {
const { sortBy, sortDesc, page, itemsPerPage } = this.options
axios.get(api.path('test')+"?"+Object.keys(this.options).map(key => key + '=' + this.options[key]).join('&'))
.then((response) => {
let items = response.data.users.data
const total = response.data.users.total
console.log(response.data.users.data)
if (sortBy.length === 1 && sortDesc.length === 1) {
items = items.sort((a, b) => {
const sortA = a[sortBy[0]]
const sortB = b[sortBy[0]]
if (sortDesc[0]) {
if (sortA < sortB) return 1
if (sortA > sortB) return -1
return 0
} else {
if (sortA < sortB) return -1
if (sortA > sortB) return 1
return 0
}
})
}
this.loading = false
resolve({
items,
total,
})
})
.catch((error) => console.log(error.message))
})
},
getDesserts () {
},
},
}
</script>
You should use computed
i'm using server pagination and search. you can inspect my code
<template>
<v-card flat>
<v-data-table
:headers="tableHead"
:items="computedFormData.items"
v-if="computedFormData && computedFormData.items"
:mobile-breakpoint="820"
v-model="selected"
:show-select="true"
:loading="loading"
:form-data="formData"
#update:page="getItemPerPage"
#update:items-per-page="getItemPerPage2"
:server-items-length="paginationTotal"
:schema="schema"
:search="search"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title class="mr-4" v-if="addHeading">{{ addHeading }}</v-toolbar-title>
</v-toolbar>
</template>
</v-data-table>
</v-card>
</template>
<script>
import {mapMutations, mapGetters, mapActions} from 'vuex'
export default {
name: 'DataTable',
components: { Button, Tab: () => import('#/components/Tabs'), Dialog: () => import('#/components/Dialog'), TableFormBuilder: () => import('#/components/Form/TableFormBuilder'), FormBuilder: () => import('#/components/Form/FormBuilder') },
props: [
'schema',
'formData',
'name',
'itemsTab',
'value',
'headers',
'title',
'nodata',
'addHeading',
'confirmDeleteTabItem',
'tableTitleOptionA',
'tableTitleOptionB',
'items'
],
data: () => ({
loading: false,
selected: [],
companyValid: true,
customerValid: true,
search: '',
dialog: false,
editedIndex: -1,
editedItem: {},
defaultItem: {}
}),
computed: {
...mapGetters('Connection', ['getConnectionPending', 'getAddFirm', 'getUpdateFirm', 'getDeleteFirm', 'getMultipleDeleteFirm', 'getCompanies']),
...mapGetters('Pagination', ['getPage']),
tableHead(){
return this.headers.filter(s => s.show);
},
computedFormData: {
get: function () {
return this.$parent.formData
},
set: function () {
return this.formData
}
},
paginationTotal: {
get: function () {
return this.$parent.formData.totalLength
}
},
tabItems: {
get: function () {
if(this.search!==''){
return this.$parent.formData.items.filter(s => s.firmaAdi === this.search)
}else{
return this.$parent.formData.items
}
},
set: function () {
return this.items
}
},
formTitle () {
return this.editedIndex === -1
? this.tableTitleOptionA
: this.tableTitleOptionB
}
},
methods: {
...mapActions("Snackbar", ["setSnackbar"]),
...mapActions("Connection", ["addFirmCall", "updateFirmCall", "deleteFirmCall", "multipleDeleteCall", "companiesCall"]),
...mapMutations('Selected', ['setSelected']),
...mapMutations('Pagination', ['setPage']),
getItemPerPage (pagination) {
this.loading=true;
this.setPage(pagination)
},
getItemPerPage2 (pagination) {
this.loading=true;
this.setPage(pagination)
},
},
watch: {
getConnectionPending(e){
this.loading=e
},
dialog(val) {
val || this.close();
},
search(e){
this.companiesCall({ page: this.getPage, limit: 10, search: e});
},
selected(e){
this.setSelected(e)
}
},
}
</script>
To late for answer, but I was looking for something similar to work with yajra/laravel-datatables this days and didn't found any examples / libs, so created something that worked:
Install composer require yajra/laravel-datatables-oracle:"~9.0" (And follow the instructions on how to add Provider, Facade, config
We will need to change our controller to Support DataTables:
use DataTables;
------
public function dataTable(Request $request){
//one line of code for simple search /sort / pagination
return DataTables::of(User::query())->make(true);
}
Next we will adjust our Vuetify component
Template
<template>
<v-data-table
:headers="headers"
:items="users"
:pagination.sync="pagination"
:total-items="totalUsers"
:rows-per-page-items="rowsPerPageItems"
:loading="loading"
>
<template v-slot:items="props">
<tr>
<td>
<div class="d-flex">
<v-btn :to="{ name: 'users.edit', params: { id: props.item.id }}">Edit</v-btn>
</div>
</td>
<td>{{ props.item.id }}</td>
<td>{{ props.item.name }}</td>
<td>{{ props.item.email }}</td>
</tr>
</template>
<template v-slot:no-results>
<v-alert :value="true" color="error" icon="warning">
Your search for "{{ searchQuery }}" found no results.
</v-alert>
</template>
</v-data-table>
</template>
JS
<script>
import axios from 'axios';
export default {
data () {
return {
draw: 1,
users: [],
searchQuery: "",
loading: true,
pagination: {
descending: true,
page: 1,
rowsPerPage: 10,
sortBy: "id",
totalItems: 0
},
totalUsers: 0,
rowsPerPageItems: [10, 15, 20, 30, 40, 50],
columns:{},
headers: [
{ text: 'Actions', value: 'actions', sortable: false, searchable: false, width: '210px'},
{ text: 'ID', value: 'id', name: 'id', sortable: true, searchable: true, width: '40px'},
{ text: 'Name', value: 'name', name: 'name', sortable: true, searchable: true, width: '250px'},
{ text: 'Email', value: 'email', sortable: true, searchable: true, width: '80px'},
],
cancelSource: null
}
},
watch: {
//watcher to watch for order/pagination and search criteria.
//
params: {
handler() {
//on params change refetch Data
//We don't do it in mounted method, becuase on first load params will change.
this.getDataFromApi().then(data => {
this.users = data.items;
this.totalUsers = data.total;
});
},
deep: true
}
},
mounted() {
//Based on our Headers we create query data for DataTables
//I've added a new param "searchable" to let DataBales know that this column is not searchable
//You can also set name as "table.column eg users.name" if you select from more then table to avoid "Ambitious column name error from SQL"
for (var i = 0; i < this.headers.length; i++) {
this.columns[i] = {
data: this.headers[i].value,
name: (typeof(this.headers[i].name) != 'undefined' ? this.headers[i].name : this.headers[i].value),
searchable: this.headers[i].searchable,
orderable: this.headers[i].sortable,
search: {
value: '',
regex: false
}
};
}
},
//computed params to return pagination and search criteria
computed: {
params(nv) {
return {
...this.pagination,
query: this.searchQuery
};
}
},
methods: {
cancelRequest() {
//Axios cancelSource to stop current search if new value is entered
if (this.cancelSource) {
this.cancelSource.cancel('Start new search, stop active search');
}
},
getDataFromApi() {
//show loading of Vuetify Table
this.loading = true;
return new Promise((resolve, reject) => {
this.cancelRequest();
this.cancelSource = axios.CancelToken.source();
//copy current params to modify
let params = this.params;
params.length = params.rowsPerPage; //set how many records to fecth per page
params.start = params.page == 1 ? 0 : (params.rowsPerPage * (params.page - 1)); //set offset
params.search = {
value: params.query,
regex: false
}; //our search query
params.draw = this.draw;
//sorting and default to column 1 (ID)
if(params.sortBy){
params.order = {
0: {
column: _.findIndex(this.headers, {
'value': params.sortBy
}),
dir: (params.descending ? 'desc' : 'asc')
}
};
}else{
params.order = {
0: {
column: 1,
dir: 'desc'
}
};
}
params.columns = this.columns; //set our previously created columns
//fecth data
//I used here jQuery $.param() helper, becuase axios submits data as JSON Payload, and we need for data or Query params
//This can be changed
axios.get('/users?'+$.param(params), {
cancelToken: this.cancelSource.token
}).then((res) => {
this.draw++;
this.cancelSource = null;
let items = res.data.data;
let total = res.data.recordsFiltered;
resolve({
items,
total
});
}).catch((err) => {
if (axios.isCancel(err)) {
console.log('Request canceled', err.message);
} else {
reject(err);
}
}).always(() => {
this.loading = false;
});
});
}
}
}
</script>
Conclusion
A simple solution to make vuetify work with Laravel DataTables, for sure is not ideal, but works well. Hope this helped.

Categories