import {serialize} from 'object-to-formdata'; import Errors from './Errors'; import cloneDeep from 'clone-deep'; 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) => { useOpnApi(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;