|
<template> |
|
<ValidationObserver v-slot="{ validate }"> |
|
<form class="needs-validation" novalidate @submit.stop.prevent=""> |
|
<div class="form-row align-items-start"> |
|
<div class="form-group col-8"> |
|
<ValidationProvider |
|
:rules="asyncRule" |
|
name="Album number" |
|
:mode="blurMode" |
|
v-slot="validatorData" |
|
> |
|
<!--<b-overlay :show="!!showOverlay" opacity="0.6" :no-fade="true">--> |
|
<input |
|
v-model.trim="chooseAlbumNumber" |
|
type="text" |
|
class="form-control mr-2" |
|
:disabled="showOverlay" |
|
placeholder="Choose your album number between 1 and 100" |
|
/> |
|
<!--</b-overlay>--> |
|
<div |
|
v-if="validatorData.errors.length > 0" |
|
class="alert alert-danger m-0 mt-1 px-2 py-1" |
|
> |
|
{{ validatorData.errors[0] }} |
|
</div> |
|
</ValidationProvider> |
|
</div> |
|
<div class="form-group col"> |
|
<button |
|
type="button" |
|
class="btn btn-primary" |
|
:disabled="showOverlay" |
|
@click.prevent="handleSend(validate)" |
|
> |
|
Send |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="form-row"> |
|
<div v-if="albumTitle" class="alert alert-success"> |
|
Album title: {{ albumTitle }} |
|
</div> |
|
</div> |
|
</form> |
|
</ValidationObserver> |
|
</template> |
|
|
|
<script> |
|
import { createAsyncQueue, createAsyncQueueData } from './rule.js'; |
|
|
|
const randomInt = max => Math.floor(Math.random() * max); |
|
// Manually slow down a fetch request. |
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); |
|
|
|
export default { |
|
data: () => ({ |
|
asyncQueue: createAsyncQueue(), |
|
// Component data: |
|
chooseAlbumNumber: '', |
|
albumTitle: '', |
|
showOverlay: false |
|
}), |
|
computed: { |
|
asyncRule() { |
|
return { |
|
required: true, |
|
// The asyncCall rule needs 2 properties: an async function and an async queue cache. |
|
asyncCall: { |
|
asyncFn: this.getAsyncDataAndRefreshUI, |
|
asyncQueue: this.asyncQueue |
|
} |
|
}; |
|
} |
|
}, |
|
methods: { |
|
blurMode() { |
|
return { |
|
on: ['blur'] |
|
}; |
|
}, |
|
async getAsyncData(asyncRuleValue) { |
|
this.albumTitle = ''; |
|
return delay(randomInt(1000)) |
|
.then(() => |
|
fetch(`https://jsonplaceholder.typicode.com/albums/${asyncRuleValue}`) |
|
) |
|
.then(response => |
|
response.ok ? response.json() : Promise.reject('Invalid request') |
|
) |
|
.then(response => { |
|
console.log('Successful fetch:', response); |
|
this.albumTitle = response.title; |
|
return createAsyncQueueData(asyncRuleValue); |
|
}) |
|
.catch( |
|
// NB: we don't really need to define an error function, |
|
// the asyncCall rule would handle rejected promises gracefully anyway. |
|
error => { |
|
console.log('Failed fetch!'); |
|
// By storing the error associated with an album number here, |
|
// we show the user the same error until he enters another album number. |
|
return createAsyncQueueData(asyncRuleValue, false, error); |
|
} |
|
); |
|
}, |
|
// We show and hide an overlay to make the asynchronous process visible to the user. |
|
async getAsyncDataAndRefreshUI(asyncRuleValue) { |
|
this.showOverlay = true; |
|
return this.getAsyncData(asyncRuleValue).finally(() => { |
|
this.showOverlay = false; |
|
}); |
|
}, |
|
handleSend(validateFormAsyncFn) { |
|
if (this.showOverlay) return; |
|
validateFormAsyncFn().then(isFormValid => { |
|
if (!isFormValid) return; |
|
// Do here whatever you want to do after a successful validation... |
|
console.log('Successful validation:', this.albumTitle); |
|
}); |
|
} |
|
} |
|
}; |
|
</script> |