2016-05-02 15:08:45 +05:30
|
|
|
import FormInputComponent from './FormInputComponent';
|
2016-05-02 14:50:50 +05:30
|
|
|
|
2016-05-02 15:08:45 +05:30
|
|
|
export default class FormModel {
|
2016-05-01 12:48:34 +05:30
|
|
|
fields = {};
|
2016-05-02 14:50:50 +05:30
|
|
|
errors = {};
|
2016-05-28 01:34:17 +05:30
|
|
|
handlers = [];
|
2016-05-01 12:48:34 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Connects form with React's component
|
|
|
|
*
|
|
|
|
* Usage:
|
|
|
|
* <input {...this.form.bindField('foo')} type="text" />
|
|
|
|
*
|
2016-07-28 09:55:02 +05:30
|
|
|
* @param {string} name - the name of field
|
2016-05-01 12:48:34 +05:30
|
|
|
*
|
2016-07-28 09:55:02 +05:30
|
|
|
* @return {object} - ref and name props for component
|
2016-05-01 12:48:34 +05:30
|
|
|
*/
|
|
|
|
bindField(name) {
|
2016-05-02 23:02:03 +05:30
|
|
|
this.fields[name] = {};
|
|
|
|
|
2016-05-02 14:50:50 +05:30
|
|
|
const props = {
|
2016-05-01 12:48:34 +05:30
|
|
|
name,
|
|
|
|
ref: (el) => {
|
2016-05-02 15:08:45 +05:30
|
|
|
if (el && !(el instanceof FormInputComponent)) {
|
|
|
|
throw new Error('Expected FormInputComponent component');
|
2016-05-02 14:50:50 +05:30
|
|
|
}
|
|
|
|
|
2016-05-01 12:48:34 +05:30
|
|
|
this.fields[name] = el;
|
|
|
|
}
|
|
|
|
};
|
2016-05-02 14:50:50 +05:30
|
|
|
|
|
|
|
if (this.getError(name)) {
|
|
|
|
props.error = this.getError(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return props;
|
2016-05-01 12:48:34 +05:30
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Focuses field
|
|
|
|
*
|
|
|
|
* @param {string} fieldId - an id of field to focus
|
|
|
|
*/
|
2016-05-01 12:48:34 +05:30
|
|
|
focus(fieldId) {
|
|
|
|
if (!this.fields[fieldId]) {
|
2016-05-15 02:23:58 +05:30
|
|
|
throw new Error(`Can not focus. The field with an id ${fieldId} does not exists`);
|
2016-05-01 12:48:34 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
this.fields[fieldId].focus();
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Get a value of field
|
|
|
|
*
|
|
|
|
* @param {string} fieldId - an id of field to get value of
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
*/
|
2016-05-01 23:20:55 +05:30
|
|
|
value(fieldId) {
|
2016-05-02 23:02:03 +05:30
|
|
|
const field = this.fields[fieldId];
|
|
|
|
|
|
|
|
if (!field) {
|
2016-05-15 02:23:58 +05:30
|
|
|
throw new Error(`Can not get value. The field with an id ${fieldId} does not exists`);
|
2016-05-01 23:20:55 +05:30
|
|
|
}
|
|
|
|
|
2016-05-02 23:02:03 +05:30
|
|
|
if (!field.getValue) {
|
|
|
|
return ''; // the field was not initialized through ref yet
|
|
|
|
}
|
|
|
|
|
|
|
|
return field.getValue();
|
2016-05-01 23:20:55 +05:30
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Add errors to form fields
|
|
|
|
*
|
|
|
|
* @param {object} errors - object maping {fieldId: errorMessage}
|
|
|
|
*/
|
2016-05-02 14:50:50 +05:30
|
|
|
setErrors(errors) {
|
|
|
|
const oldErrors = this.errors;
|
|
|
|
this.errors = errors;
|
|
|
|
|
|
|
|
Object.keys(this.fields).forEach((fieldId) => {
|
|
|
|
if (oldErrors[fieldId] || errors[fieldId]) {
|
|
|
|
this.fields[fieldId].setError(errors[fieldId] || null);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Get error by id
|
|
|
|
*
|
|
|
|
* @param {string} fieldId - an id of field to get error for
|
|
|
|
*
|
|
|
|
* @return {string|null}
|
|
|
|
*/
|
2016-05-02 14:50:50 +05:30
|
|
|
getError(fieldId) {
|
|
|
|
return this.errors[fieldId] || null;
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* @return {bool}
|
|
|
|
*/
|
2016-05-22 13:23:40 +05:30
|
|
|
hasErrors() {
|
|
|
|
return Object.keys(this.errors).length > 0;
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Convert form into key-value object representation
|
|
|
|
*
|
|
|
|
* @return {object}
|
|
|
|
*/
|
2016-05-01 12:48:34 +05:30
|
|
|
serialize() {
|
2016-05-02 14:50:50 +05:30
|
|
|
return Object.keys(this.fields).reduce((acc, fieldId) => {
|
|
|
|
acc[fieldId] = this.fields[fieldId].getValue();
|
2016-05-01 12:48:34 +05:30
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
}
|
2016-05-28 01:34:17 +05:30
|
|
|
|
|
|
|
/**
|
|
|
|
* Bind handler to listen for form loading state change
|
|
|
|
*
|
2016-07-28 09:55:02 +05:30
|
|
|
* @param {function} fn
|
2016-05-28 01:34:17 +05:30
|
|
|
*/
|
|
|
|
addLoadingListener(fn) {
|
|
|
|
this.removeLoadingListener(fn);
|
|
|
|
this.handlers.push(fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove form loading state handler
|
|
|
|
*
|
2016-07-28 09:55:02 +05:30
|
|
|
* @param {function} fn
|
2016-05-28 01:34:17 +05:30
|
|
|
*/
|
|
|
|
removeLoadingListener(fn) {
|
|
|
|
this.handlers = this.handlers.filter((handler) => handler !== fn);
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Switch form in loading state
|
|
|
|
*/
|
2016-05-28 01:34:17 +05:30
|
|
|
beginLoading() {
|
2016-05-28 03:06:10 +05:30
|
|
|
this._isLoading = true;
|
|
|
|
this.notifyHandlers();
|
2016-05-28 01:34:17 +05:30
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* Disable loading state
|
|
|
|
*/
|
2016-05-28 01:34:17 +05:30
|
|
|
endLoading() {
|
2016-05-28 03:06:10 +05:30
|
|
|
this._isLoading = false;
|
|
|
|
this.notifyHandlers();
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:55:02 +05:30
|
|
|
/**
|
|
|
|
* @api private
|
|
|
|
*/
|
2016-05-28 03:06:10 +05:30
|
|
|
notifyHandlers() {
|
|
|
|
this.handlers.forEach((fn) => fn(this._isLoading));
|
2016-05-28 01:34:17 +05:30
|
|
|
}
|
2016-05-01 12:48:34 +05:30
|
|
|
}
|