cosmo-ui
Version:
Common React components
217 lines • 9.56 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var _1 = require(".");
var cx = require("classnames");
var PropTypes = require("prop-types");
var data_1 = require("../data");
var styles = require('../../src/styles/components/forms.scss');
var borderStyles = require('../../src/styles/common/borders.scss');
/**
* A wrapper component for form fields that
* reads the form name of parent <Form /> elements
*/
var FormFieldWrapper = (function (_super) {
tslib_1.__extends(FormFieldWrapper, _super);
function FormFieldWrapper() {
return _super !== null && _super.apply(this, arguments) || this;
}
FormFieldWrapper.prototype.componentWillMount = function () {
// console.log('FORM FIELD WRAPPER WILL MOUNT', this)
var name = this.context
? this.context.formName
: this.props.formName;
if (name) {
this.formName = name;
}
};
return FormFieldWrapper;
}(React.Component));
FormFieldWrapper.contextTypes = {
formName: PropTypes.string,
};
exports.FormFieldWrapper = FormFieldWrapper;
/**
* The base form field component is responsible for:
*
* - initializing field data using a set of standard fields (AbstractFormField)
* - dispatching events and updating the store
* - running the validators prior to updating the store
* - rendering error messages underneath the field
* - styling success and errors in red/green
*
* All of these are intedend to be handled in a standard way and thus
* have been delegated to this class. Every class that extends this one
* must implement the renderField method to render a specific input type
*/
var BaseFormField = (function (_super) {
tslib_1.__extends(BaseFormField, _super);
function BaseFormField(props) {
var _this = _super.call(this, props) || this;
/**
* Use React refs to set the field elem
*/
_this.setFieldRef = function (e) { return _this.field = e; };
_this.validate = _this.props.validate ?
_this.props.validate.bind(_this) : _this.validate.bind(_this);
_this.onChange = _this.onChange.bind(_this);
_this.onBlur = _this.onBlur.bind(_this);
_this.onFocus = _this.onFocus.bind(_this);
return _this;
}
/**
* Initialize the field
* this will also trigger the validation
* but will not mark the field as dirty (only onFocus does that)
*/
BaseFormField.prototype.componentDidMount = function () {
// console.log('FORM FIELD DID MOUNT', this)
this.dispatchUpdate(this.getNextFieldState(this.getValueFromState()));
if (this.props.autofocus) {
this.field.focus();
}
};
BaseFormField.prototype.render = function () {
// console.log('RENDER FORM FIELD', this)
// the field may not be initialized yet
if (!this.props.field) {
return null;
}
return (React.createElement(_1.Column, { className: styles.fieldset },
this.renderField(),
React.createElement(_1.Row, { align: "flex-start", className: styles.errorText }, this.renderErrors())));
};
BaseFormField.prototype.renderErrors = function () {
if (this.shouldShowErrors()) {
var errors = this.props.field.errors;
return React.createElement("span", null, errors[0]);
}
};
/**
* Default validation function
* Checks to see if the field is required
* And if a value is present
*
* @param value
*/
BaseFormField.prototype.validate = function (value) {
if (this.props.field.required && !value) {
return ['* required'];
}
return [];
};
/**
* This function instructs the class in how to create a blank field
* It may be different for different types of form fields
* Since a lot of fields are based on text input a default version
* has been provided that uses the empty string as a default value
*/
BaseFormField.prototype.initializeField = function () {
var _a = this.props, value = _a.value, required = _a.required;
return data_1.createEmptyFormField(value || '', required === false ? false : true, false);
};
/**
* Create an object representing the next state state of the field
*
* @param value
*/
BaseFormField.prototype.getNextFieldState = function (value) {
var field = tslib_1.__assign({}, this.props.field, { value: value });
// do not mess with the validation if an async validation function is present
if (!this.props.asyncValidate) {
// people might think that the validation function is supposed
// to return a boolean rather than an array of string errors
// rather than leave this as a potential bug better to support it here
var res = this.validate(value);
field.errors = res === true ? [] : (res === false ? ['* error'] : res);
field.valid = field.errors && field.errors.length ? false : true;
}
return field;
};
BaseFormField.prototype.onChange = function (e) {
e.preventDefault();
var _a = this.props, name = _a.name, formName = _a.formName, onChange = _a.onChange, asyncOnChange = _a.asyncOnChange;
// console.log('handling change', this.props)
var field = this.getNextFieldState(this.getValueFromEvent(e));
if (onChange) {
onChange(name, formName, field);
}
if (asyncOnChange) {
asyncOnChange(name, formName, field);
}
return this.dispatchUpdate(field);
};
BaseFormField.prototype.onBlur = function (e) {
e.preventDefault();
var field = tslib_1.__assign({}, this.props.field, { focused: false });
if (this.props.onBlur) {
this.props.onBlur(this.props.name, this.props.formName, field);
}
return this.dispatchUpdate(field);
};
BaseFormField.prototype.onFocus = function (e) {
e.preventDefault();
var field = tslib_1.__assign({}, this.props.field, { focused: true, dirty: true });
if (this.props.onFocus) {
this.props.onFocus(this.props.name, this.props.formName, field);
}
return this.dispatchUpdate(field);
};
BaseFormField.prototype.dispatchUpdate = function (field) {
this.props.setFormField(this.props.name, this.props.formName, field);
};
BaseFormField.prototype.getValueFromEvent = function (e) {
return e.currentTarget.value;
};
/**
* Decide which value to use - props.value or props.field.value
* This distinction is there because some inputs are 'semi-controlled'
* This happens when setting and updating the value is handled by the client
* but fields such as dirty, valid, focused etc are handled by cosmo ui
*
* NOTE: this function should not be typed because in the case of some inputs
* such as the number input, it is saved as a number in the state but it is
* inputted as a string
*/
BaseFormField.prototype.getValueFromState = function () {
var _a = this.props, value = _a.value, onChange = _a.onChange, field = _a.field;
// if both a value and an onChange handler are provided
// then the client wishes to control the field manually
// otherwise assume the version provided by cosmo-ui formReducer is in play
return value && onChange ? value : field.value;
};
BaseFormField.prototype.shouldShowErrors = function () {
var _a = this.props.field, valid = _a.valid, dirty = _a.dirty, submitted = _a.submitted, focused = _a.focused;
// to show errors then the field must be invalid and either submitted or
// dirty and not focused (if it's focused the user is changing stuff)
return !valid && (submitted || (dirty && !focused));
};
BaseFormField.prototype.shouldShowValid = function () {
var _a = this.props.field, valid = _a.valid, dirty = _a.dirty, submitted = _a.submitted, focused = _a.focused, required = _a.required, errors = _a.errors;
// obviously it becoes visible if its dirty or submitted
// but if the field isn't required then we won't show any colour
// at all whilst it's still pristine (hence the required part here)
var visible = (required || dirty || submitted);
return !this.props.disabled && visible && valid && !errors.length;
};
BaseFormField.prototype.classNames = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _a = this.props.field, valid = _a.valid, errors = _a.errors, focused = _a.focused, required = _a.required;
var res = cx.apply(void 0, [styles.field].concat(args, [this.props.className, (_b = {},
_b[styles.disabled] = this.props.disabled === true,
_b[borderStyles.error] = !this.props.disabled && this.shouldShowErrors(),
_b[borderStyles.success] = this.shouldShowValid(),
_b[borderStyles.info] = !this.props.disabled && focused && !valid,
_b)]));
// console.log('classnames', this.props, res)
return res;
var _b;
};
return BaseFormField;
}(React.Component));
exports.BaseFormField = BaseFormField;
//# sourceMappingURL=base-form-field.js.map