Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions assets-public/js/ajax_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import axios from 'axios'
import addToast from 'sumocoders/addToast'

class AjaxClient {
constructor () {
const options = {
transformRequest: [
(data, header) => {
if (this.instance.csrf_token) {
data.csrf_token = this.instance.csrf_token
}
return data
}, ...axios.defaults.transformRequest
]
}

this.instance = axios.create(options)
this.configureDefaults()
this.configureInterceptors()
}

configureDefaults () {
this.instance.defaults.timeout = 2500
this.instance.defaults.headers.common = {
'Accept': 'application/json',
}
}

configureInterceptors () {
this.instance.interceptors.response.use(
// Successful request
function (response) {
// Any status code that lie *within* the range of 2xx cause this function to trigger

if (response.data.disable_interceptor) {
return response
}

if (response.data.message) {
addToast(response.data.message, 'success')
}

return response
},
function (error) {
// Any status codes that falls *outside* the range of 2xx cause this function to trigger

if (error.response.data.disable_interceptor) {
return Promise.reject(error)
}

console.error(error)

if (error.response.data.message) {
addToast(error.response.data.message, 'danger')

return Promise.reject(error)
}

if (error.message) {
addToast(error.message, 'danger')

return Promise.reject(error)
}

return Promise.reject(error)
})
}
}

const instance = new AjaxClient()

export default instance.instance
127 changes: 127 additions & 0 deletions docs/frontend/ajax-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# AJAX client

The AJAX client is a simple wrapper around [`Axios`](https://axios-http.com/).

## Default Axios Config

Some configuration is done by default. But it can be overruled.

* `timeout`: 2500
* `headers.common`: `Accept: application/json`

## Usage

The AJAX Client is just an extended version of the Axios client all Axios
documentation is still valid. For the full documentatuon see
[https://axios-http.com/docs/intro](https://axios-http.com/docs/intro).

A simplified Stimulus controller that uses the AJAX Client:

```javascript
import { Controller } from '@hotwired/stimulus'
import ajaxClient from '../js/ajax_client.js'

export default class extends Controller {
static values = {
url: String,
csrfToken: String
}

test () {
let data = {
foo: 'bar',
}

ajaxClient.csrf_token = this.csrfTokenValue
ajaxClient.post(this.urlValue, data)
.then(response => {
// do something with the response
...
})
.catch(error => {
// do something with the error
...
})
}
}
```

## Default toasts

Depending on HTTP status code and the provided data a toast will be shown. This
is done by using [Interceptors](https://axios-http.com/docs/interceptors). So
you can still use the promises of the Axios client.

### Success (HTTP Status 2XX)

If the response object contains a `message` key, a success toast will be shown.

You can return a response like this:

```php
return new JsonResponse(
[
'message' => 'The action was successful.',
],
200
);
```

The actual JSON will be:

```json
{
"message": "The action was successful."
}
```

### Error (HTTP Status != 2XX)

If the response object contains a `message` key, an danger toast will be shown.
If the message is not present the Exception message will be used.

```php
return new JsonResponse(
[
'message' => 'Item not found.',
],
404
);
```

or

```php
throw new \RuntimeException('Item not found.');
```

### Disable this behavior

You can disable the toast by passing a `disable_interceptor: false` in the response data.

```json
{
"message": "The action was successful",
"disable_interceptor": true
}
```

## CSRF token

A simple way to "protect" the AJAX calls is by using a CSRF token. This is done like below:

```javascript
ajaxClient.csrf_token = this.csrfTokenValue
ajaxClient.post(this.urlValue, data)
...
```

With this the csrf token is added to the payload of the request, with the key `csrf_token`.

In your controller you will need to check if the CSRF token is valid:

```php
if (!$this->isCsrfTokenValid('this-is-our-csrf-token-id', $request->getPayload()->get('csrf_token'))) {
throw new InvalidCsrfTokenException('Invalid CSRF token');
}
```