WIP
This commit is contained in:
75
client/composables/lib/vForm/Errors.js
vendored
Normal file
75
client/composables/lib/vForm/Errors.js
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
function arrayWrap(value) {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
export default class Errors {
|
||||
constructor() {
|
||||
this.errors = {};
|
||||
}
|
||||
|
||||
set(field, messages = undefined) {
|
||||
if (typeof field === 'object') {
|
||||
this.errors = field;
|
||||
} else {
|
||||
this.set({ ...this.errors, [field]: arrayWrap(messages) });
|
||||
}
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
has(field) {
|
||||
return Object.prototype.hasOwnProperty.call(this.errors, field);
|
||||
}
|
||||
|
||||
hasAny(...fields) {
|
||||
return fields.some(field => this.has(field));
|
||||
}
|
||||
|
||||
any() {
|
||||
return Object.keys(this.errors).length > 0;
|
||||
}
|
||||
|
||||
get(field) {
|
||||
if (this.has(field)) {
|
||||
return this.getAll(field)[0];
|
||||
}
|
||||
}
|
||||
|
||||
getAll(field) {
|
||||
return arrayWrap(this.errors[field] || []);
|
||||
}
|
||||
|
||||
only(...fields) {
|
||||
const messages = [];
|
||||
|
||||
fields.forEach((field) => {
|
||||
const message = this.get(field);
|
||||
if (message) {
|
||||
messages.push(message);
|
||||
}
|
||||
});
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
flatten() {
|
||||
return Object.values(this.errors).reduce((a, b) => a.concat(b), []);
|
||||
}
|
||||
|
||||
clear(field = undefined) {
|
||||
const errors = {};
|
||||
|
||||
if (field) {
|
||||
Object.keys(this.errors).forEach((key) => {
|
||||
if (key !== field) {
|
||||
errors[key] = this.errors[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.set(errors);
|
||||
}
|
||||
}
|
||||
175
client/composables/lib/vForm/Form.js
vendored
Normal file
175
client/composables/lib/vForm/Form.js
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
import {serialize} from 'object-to-formdata';
|
||||
import Errors from './Errors';
|
||||
import cloneDeep from 'clone-deep';
|
||||
import {useOpnFetch} from "~/composables/useOpnFetch.js";
|
||||
|
||||
function hasFiles(data) {
|
||||
return data instanceof File ||
|
||||
data instanceof Blob ||
|
||||
data instanceof FileList ||
|
||||
(typeof data === 'object' && data !== null && Object.values(data).find(value => hasFiles(value)) !== undefined);
|
||||
}
|
||||
|
||||
class Form {
|
||||
constructor(data = {}) {
|
||||
this.originalData = {};
|
||||
this.busy = false;
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
this.recentlySuccessfulTimeoutId = undefined;
|
||||
this.errors = new Errors();
|
||||
this.update(data);
|
||||
}
|
||||
|
||||
static errorMessage = 'Something went wrong. Please try again.';
|
||||
static recentlySuccessfulTimeout = 2000;
|
||||
static ignore = ['busy', 'successful', 'errors', 'originalData', 'recentlySuccessful', 'recentlySuccessfulTimeoutId'];
|
||||
|
||||
static make(augment) {
|
||||
return new this(augment);
|
||||
}
|
||||
|
||||
update(data) {
|
||||
this.originalData = Object.assign({}, this.originalData, cloneDeep(data));
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
fill(data = {}) {
|
||||
this.keys().forEach((key) => {
|
||||
this[key] = data[key];
|
||||
});
|
||||
}
|
||||
|
||||
data() {
|
||||
return this.keys().reduce((data, key) => (
|
||||
{...data, [key]: this[key]}
|
||||
), {});
|
||||
}
|
||||
|
||||
keys() {
|
||||
return Object.keys(this).filter(key => !Form.ignore.includes(key));
|
||||
}
|
||||
|
||||
startProcessing() {
|
||||
this.errors.clear();
|
||||
this.busy = true;
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId);
|
||||
}
|
||||
|
||||
finishProcessing() {
|
||||
this.busy = false;
|
||||
this.successful = true;
|
||||
this.recentlySuccessful = true;
|
||||
this.recentlySuccessfulTimeoutId = setTimeout(() => {
|
||||
this.recentlySuccessful = false;
|
||||
}, Form.recentlySuccessfulTimeout);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.errors.clear();
|
||||
this.successful = false;
|
||||
this.recentlySuccessful = false;
|
||||
clearTimeout(this.recentlySuccessfulTimeoutId);
|
||||
}
|
||||
|
||||
reset() {
|
||||
Object.keys(this)
|
||||
.filter(key => !Form.ignore.includes(key))
|
||||
.forEach((key) => {
|
||||
this[key] = deepCopy(this.originalData[key]);
|
||||
});
|
||||
}
|
||||
|
||||
get(url, config = {}) {
|
||||
return this.submit('get', url, config);
|
||||
}
|
||||
|
||||
post(url, config = {}) {
|
||||
return this.submit('post', url, config);
|
||||
}
|
||||
|
||||
patch(url, config = {}) {
|
||||
return this.submit('patch', url, config);
|
||||
}
|
||||
|
||||
put(url, config = {}) {
|
||||
return this.submit('put', url, config);
|
||||
}
|
||||
|
||||
delete(url, config = {}) {
|
||||
return this.submit('delete', url, config);
|
||||
}
|
||||
|
||||
submit(method, url, config = {}) {
|
||||
this.startProcessing();
|
||||
|
||||
config = {
|
||||
body: {},
|
||||
params: {},
|
||||
url: url,
|
||||
method: method,
|
||||
...config
|
||||
};
|
||||
|
||||
if (method.toLowerCase() === 'get') {
|
||||
config.params = {...this.data(), ...config.params};
|
||||
} else {
|
||||
config.body = {...this.data(), ...config.data};
|
||||
|
||||
if (hasFiles(config.data) && !config.transformRequest) {
|
||||
config.transformRequest = [data => serialize(data)];
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
useOpnFetch(config.url, config)
|
||||
.then(({data, error}) => {
|
||||
if (error.value) {
|
||||
this.handleErrors(error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.finishProcessing();
|
||||
resolve(data.value);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
handleErrors(error) {
|
||||
this.busy = false;
|
||||
|
||||
if (error.value) {
|
||||
this.errors.set(this.extractErrors(error.value.data));
|
||||
}
|
||||
}
|
||||
|
||||
extractErrors(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return {error: Form.errorMessage};
|
||||
|
||||
}
|
||||
|
||||
if (data.errors) {
|
||||
return {...data.errors};
|
||||
}
|
||||
|
||||
if (data.message) {
|
||||
return {error: data.message};
|
||||
}
|
||||
|
||||
return {...data};
|
||||
}
|
||||
|
||||
onKeydown(event) {
|
||||
const target = event.target;
|
||||
|
||||
if (target.name) {
|
||||
this.errors.clear(target.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Form;
|
||||
5
client/composables/useForm.js
vendored
Normal file
5
client/composables/useForm.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import Form from "~/composables/lib/vForm/Form.js"
|
||||
|
||||
export const useForm = (formData) => {
|
||||
return new Form(formData)
|
||||
}
|
||||
5
client/composables/useOpnFetch.js
vendored
Normal file
5
client/composables/useOpnFetch.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import config from "~/opnform.config.js";
|
||||
|
||||
export const useOpnFetch = (request, opts) => {
|
||||
return useFetch(request, { baseURL: config.api_url, ...opts })
|
||||
}
|
||||
Reference in New Issue
Block a user