I am building a datatable with multi-column sorting functionality. As up to now, the sorting functionality is working fine, what I am unable to get is, right parameters into the url. As I am only passing $sorts to the component, as a prop, hence I'm using this.$inertia.get to pass the $sorts back to the controller, which is returning back the sorted data. But due to passing sorts: this.sorts within the Inertia get method, its returning back the url query as http://127.0.0.1:8000/users?sorts[name]=asc. How can I get the required parameter within the Inertia get method so I get a url query as suchhttp://127.0.0.1:8000/users?sort_field=name&sort_direction=asc as well as pass the $sorts as well so it returns back the expected data.
Controller
public $sorts = [];
public function initalizeSortingRequest()
{
$this->sorts = request()->get('sorts', $this->sorts);
}
public function applySorting($query)
{
foreach ($this->sorts as $sort_field => $sort_direction) {
$query->orderBy($sort_field, $sort_direction);
}
return $query;
}
Component
<script >
methods: {
sortBy(field) {
if (!this.sorts[field]) {
this.sorts[field] = 'asc';
} else if (this.sorts[field] == 'asc') {
this.sorts[field] = 'desc';
} else {
delete this.sorts[field];
}
let route = this.route('users.index', {
sorts: this.sorts
})
this.$inertia.get(route, {}, {
only: ['usersData'],
preserveState: true,
preserveScroll: true
})
}
}
</script>
I recently made a screencast on building a datatable with InertiaJS and Laravel.
The gist of it is:
import AppLayout from '#/Layouts/AppLayout';
import Pagination from '../Jetstream/Pagination';
import { pickBy, throttle } from 'lodash';
export default {
components: {
AppLayout,
Pagination,
},
props: {
users: Object,
filters: Object,
},
data() {
return {
params: {
search: this.filters.search,
field: this.filters.field,
direction: this.filters.direction,
},
};
},
methods: {
sort(field) {
this.params.field = field;
this.params.direction = this.params.direction === 'asc' ? 'desc' : 'asc';
},
},
watch: {
params: {
handler: throttle(function () {
let params = pickBy(this.params);
this.$inertia.get(this.route('users'), params, { replace: true, preserveState: true });
}, 150),
deep: true,
},
},
};
Then in the controller index action:
public function index()
{
request()->validate([
'direction' => ['in:asc,desc'],
'field' => ['in:name,city']
]);
$query = User::query();
if (request('search')) {
$query->where('name', 'LIKE', '%'.request('search').'%');
}
if (request()->has(['field', 'direction'])) {
$query->orderBy(request('field'), request('direction'));
}
return Inertia::render('Users', [
'users' => $query->paginate()->withQueryString(),
'filters' => request()->all(['search', 'field', 'direction'])
]);
}
You can watch the screencast here.
Related
first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']
Trying to make a reactive query as per https://v4.apollo.vuejs.org/guide-option/queries.html#reactive-query-definition, but I can't get them to work.
Error:
Uncaught (in promise) Error: Invalid AST Node: { query: { kind: "Document", definitions: [Array], loc: [Object] }, loadingKey: "loading" }.
Query inside export default (checked is a reactive boolean v-model'ed to a button defined in data()):
apollo: {
getTags: getTags,
getPhotos: {
query() {
if (this.checked)
return {
query: getPhotos,
loadingKey: "loading",
}
else {
return {
query: getPhotosByTag,
loadingKey: "loading",
}
}
},
update: (data) => data.getPhotos || data.getPhotos,
},
},
GQL getPhotosByTag:
const getPhotosByTag = gql`
query getPhotosByTag {
getPhotos: findTagByID(id: 326962542206255296) {
photos {
data {
name
description
_id
}
}
}
}
`
GQL getPhotos:
const getPhotos = gql`
query getPhotos {
getPhotos: getPhotos(_size: 50) {
data {
name
description
_id
}
}
}
`
If I take them out into separate queries and try to instead update use skip() via checked and !checked in the query definition, only the initial load delivers a query, if I click the button new query doesn't launch. Queries work by themselves fine.
apollo: {
getPhotos: {
query() {
return this.checked ? getPhotos : getPhotosByTag
},
loadingKey: "loading",
update: (data) => data.getPhotos || data.getPhotos,
},
},
Loading key has to be on the same level as query
I'm building an application with Vue on the frontend and Laravel PHP on the backend. Its a single page app (SPA).
When changing pages, sometimes - not always - axios makes two requests for the same page. I'm having trouble figure it out what is happening.
When the link changes I have two watchers, one for the top category and another for the sub-category. They trigger the created () hook that calls the loadData method.
If I change the main category and sub category ( Example: from 1/5 to 2/31 ), the loadData method is called two time. How can I fix this ?
Google Network Tab (The .json request does not represent the same type of page that I'm referring above, only the numbers ) :
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
import Form from 'vform'
export default {
data() {
return {
products: {
cat : {} ,
products : []
},
productsShow: '',
quickSearchQuery: '',
loadeddata : false,
}
},
methods: {
loadMore () {
this.productsShow += 21
},
loadData () {
if ( this.$route.params.sub_id ) {
axios.get('/api/cat/' + this.$route.params.cat_id + '/' + this.$route.params.sub_id).then(response => {
this.products = response.data
this.productsShow = 21
this.loadeddata = true
}).catch(error => {
if (error.response.status === 404) {
this.$router.push('/404');
}
});
} else {
axios.get('/api/cat/' + this.$route.params.cat_id ).then(response => {
this.products = response.data
this.productsShow = 21
this.loadeddata = true
}).catch(error => {
if (error.response.status === 404) {
this.$router.push('/404');
}
});
}
},
computed: {
...mapGetters({locale: 'lang/locale', locales: 'lang/locales' , user: 'auth/user'}),
filteredRecords () {
let data = this.products.products
data = data.filter( ( row ) => {
return Object.keys( row ).some( ( key ) => {
return String( row[key] ).toLowerCase().indexOf(this.quickSearchQuery.toLowerCase()) > -1
})
})
return data
}
},
created() {
this.loadData()
},
watch: {
'$route.params.cat_id': function (cat_id) {
this.quickSearchQuery = ''
this.loadData()
},
'$route.params.sub_id': function (sub_id) {
this.quickSearchQuery = ''
this.loadData()
}
}
}
</script>
So I see that you figured the issue by yourself.
To fix that, you can delay the loadData function (kind of debounce on the tail) so if it's being called multiple times in less than X (300ms?) then only the last execution will run.
try:
loadData () {
clearTimeout(this.loadDataTimeout);
this.loadDataTimeout = setTimeout(()=> {
//The rest of the loadData function should be placed here
},300);
}
Resolved :
watch: {
'$route.params': function (cat_id, sub_id) {
this.quickSearchQuery = ''
this.loadData()
},
I have been working on the feature of comment deleting and came across a question regarding a mutation for an action.
Here is my client:
delete_post_comment({post_id, comment_id} = {}) {
// DELETE /api/posts/:post_id/comments/:id
return this._delete_request({
path: document.apiBasicUrl + '/posts/' + post_id + '/comments/' + comment_id,
});
}
Here is my store:
import Client from '../client/client';
import ClientAlert from '../client/client_alert';
import S_Helper from '../helpers/store_helper';
const state = {
comment: {
id: 0,
body: '',
deleted: false,
},
comments: [],
};
const actions = {
deletePostComment({ params }) {
// DELETE /api/posts/:post_id/comments/:id
document.client
.delete_post_comment({ params })
.then(ca => {
S_Helper.cmt_data(ca, 'delete_comment', this);
})
.catch(error => {
ClientAlert.std_fail_with_err(error);
});
},
};
delete_comment(context, id) {
context.comment = comment.map(comment => {
if (!!comment.id && comment.id === id) {
comment.deleted = true;
comment.body = '';
}
});
},
};
export default {
state,
actions,
mutations,
getters,
};
I am not quite sure if I wrote my mutation correctly. So far, when I am calling the action via on-click inside the component, nothing is happening.
Guessing you are using vuex the flow should be:
according to this flow, on the component template
#click="buttonAction(someParams)"
vm instance, methods object:
buttonAction(someParams) {
this.$store.dispatch('triggerActionMethod', { 'something_else': someParams })
}
vuex actions - Use actions for the logic, ajax call ecc.
triggerActionMethod: ({commit}, params) => {
commit('SOME_TRANSATION_NAME', params)
}
vuex mutations - Use mutation to make the changes into your state
'SOME_TRANSATION_NAME' (state, data) { state.SOME_ARG = data }
I call a function with this code,
gqlActions('customer', 'Add', this.props, values);
or
gqlActions('customer', 'Update', this.props, values);
this funcions is used for add and update actions.
On the function I use computed property, for example in
const tableAction= `${table}${action}`;
[tableAction]: valuesOptimistic,
It's working ok, my problem is in destructuring before, to use that variable after:
update: (store, { data: { [tableAction] }}) => {
data.customers.push([tableAction]);
it's not valid syntax... , before i've used hardcode for 'Add' action :
update: (store, { data: { customerAdd }}) => {
data.customers.push(customerAdd);
},
or
update: (store, { data: { customerUpdate }}) => {
data.customers.push(customerUpdate);
},
becase I send 'update' property to work for a library that sends me the value accord to [tableAction] that I've defined in:
optimisticResponse: {
[tableAction]: valuesOptimistic,
}
I mean parameter in denormalization is variable (update or add). I hope be clear.
my full function:
export const gqlActions = (table, action, props, values) => {
const valuesOptimistic = {
...Object.assign({}, values, __typename: table'})
};
const tableAction= `${table}${action}`;
props.mutate(
{
variables: values,
optimisticResponse: {
[tableAction]: valuesOptimistic,
},
update: (store, { data: { [tableAction] }}) => {
data.customers.push([tableAction]);
},
},
)
}
}
You need to use destructuring using computed property names
update: (store, { data: { [tableAction]:action }}) => {
data.customers.push(action);
}