framework-entersol-web
Version:
Framework based on bootstrap 5
277 lines (256 loc) • 9.06 kB
JSX
import React, { Fragment, createRef } from "react";
import PropTypes from "prop-types";
import { randomS4 } from "../../functions";
import eventHandler from "../../functions/event-handler";
import Component from "../../component";
export default class Field extends Component {
static jsClass = 'Field';
static propTypes = {
...Component.propTypes,
autoComplete: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
checkValidity: PropTypes.func,
controlClasses: PropTypes.string,
default: PropTypes.any,
disabled: PropTypes.bool,
readOnly: PropTypes.bool,
errorMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.node]),
first: PropTypes.oneOf(['label', 'control']),
inline: PropTypes.bool,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
labelClasses: PropTypes.string,
max: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
min: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
noValidate: PropTypes.bool,
options: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.bool]),
value: PropTypes.any,
disabled: PropTypes.bool,
divider: PropTypes.bool
})),
pattern: PropTypes.string,
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
required: PropTypes.bool,
step: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
type: PropTypes.string.isRequired,
value: PropTypes.any,
accept: PropTypes.string,
message: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.node]),
floating: PropTypes.bool
}
static defaultProps = {
...Component.defaultProps,
type: 'text',
default: '',
value: '',
first: 'label',
floating: true
}
unique = randomS4();
state = {
value: this.props.value || this.props.default,
options: this.props.options,
error: false
}
ContentWrap = 'div';
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onInvalid = this.onInvalid.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.input = createRef();
}
componentDidMount() {
eventHandler.subscribe('update.' + this.props.name, this.onUpdate, this.unique);
eventHandler.dispatch('ready.' + this.props.name);
}
componentWillUnmount() {
clearTimeout(this.timeout);
eventHandler.unsubscribe('update.' + this.props.name, this.unique);
}
extractString(obj) {
if (typeof obj === 'string') return obj;
else if (Array.isArray(obj)) {
return obj.map(e => this.extractString(e)).join(' ');
} else if (React.isValidElement(obj)) {
return this.extractString(obj.props.children);
} else if (!obj) return '';
return obj.toString();
}
returnData(value = this.state.value, extra) {
let { name, id, data } = this.props;
let { error } = this.state;
const toDispatch = { [name]: value };
if (id) toDispatch.id = id;
if (data) toDispatch.data = data;
if (this._reset) this._reset = false;
else if (!error) {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
eventHandler.dispatch(name, toDispatch, extra);
}, 300);
}
}
isInvalid(value) {
let { checkValidity, pattern, required } = this.props;
let inputValid = true;
let customInput = this.input.current?.querySelector('input') || this.input.current
customInput?.setCustomValidity('');
if (typeof customInput?.checkValidity === 'function') {
inputValid = customInput.checkValidity();
}
let valueInvalid = !value;
if (typeof value === 'boolean' || typeof value === 'number') {
valueInvalid = false;
}
let error = (!inputValid || (required && valueInvalid));
if (!error && typeof checkValidity === 'function')
error = !checkValidity(value);
else if (pattern) error = !(new RegExp(pattern, "i")).test(value);
if (!required && !value) error = false;
if (error) {
const errorMessage = this.extractString(this.props.errorMessage);
customInput?.setCustomValidity(errorMessage);
}
return error;
}
onInvalid() {
const { name, required } = this.props;
const { value } = this.state;
if (!required && !value) return;
this.setState(
{ error: true },
() => eventHandler.dispatch('invalid.' + name, { [name]: value })
);
}
onChange(e) {
let { value } = e.target;
let error;
if(this.props.name === 'orden_compra') error = false
else error = this.isInvalid(value);
this.setState({
value,
error
}, () => this.returnData());
}
onUpdate({ value, options, error, reset }) {
const newState = {};
if (typeof value !== 'undefined')
newState.value = (value !== null ? value : this.props.default);
if (options) newState.options = options;
if (typeof error === 'boolean') {
newState.error = error;
let message = '';
if (error) message = this.extractString(this.props.errorMessage);
this.input.current.setCustomValidity(message);
}
if (reset) {
newState.value = newState.value || this.props.default;
this._reset = true;
return this.setState(newState, this.returnData);
}
this.setState(newState, () => {
if (this.input.current && typeof value !== 'undefined') {
setTimeout(() => {
const error = this.isInvalid(value);
if (this.state.error !== error) this.setState({ error });
}, 300);
}
});
}
onFocus = () => {
const { name } = this.props;
eventHandler.dispatch('focus.' + name);
}
get type() {
return this.props.type;
}
get inputProps() {
const { disabled, readOnly, accept, minLength,
required, name, controlClasses, maxLength, list,
placeholder: prePlaceholder, step, noValidate, multiple, autoComplete,
min, max, pattern, dir, _propsControl = {} } = this.props;
const { value, error } = this.state;
const cn = [
'form-control',
controlClasses, error ? 'is-invalid' : ''
];
if (autoComplete === false) {
var autocomplete = 'off';
var list1 = 'autocompleteOff';
}
const placeholder = !!prePlaceholder ? this.extractString(prePlaceholder) : null;
return {
id: name, name, autoComplete: autocomplete || autoComplete,
list: list1 || list, pattern, placeholder,
required, type: this.type,
value, className: cn.join(' '),
min, max, step, noValidate, disabled,
readOnly, ref: this.input, dir, accept,
multiple, maxLength, minLength,
onChange: this.onChange,
onInvalid: this.onInvalid,
onFocus: this.onFocus,
..._propsControl
}
}
get labelNode() {
const { placeholder, required, name, labelClasses,
inline, label, disabled } = this.props;
const cn = ['form-label', labelClasses];
if (inline) { cn.shift(); cn.push('py-2') }
const style = {};
if (disabled) style['opacity'] = .9;
const labelNode = <label className={cn.join(' ')} htmlFor={name} style={style}>
{label ? label : placeholder}
{required && <b title="Este campo es indispensable" className="text-inherit"> *</b>}
</label>
return (labelNode);
}
get inputNode() {
const inputNode = (<input {...this.inputProps} />);
return inputNode;
}
get errorMessageNode() {
const { errorMessage } = this.props;
const { error } = this.state;
const errorNode = (<p className="m-1 lh-1"><small className="text-danger">
{errorMessage}
</small></p>);
return (error && errorMessage && errorNode);
}
get messageNode() {
const { message, messageClasses } = this.props;
const cnm = ['m-1 lh-1'];
if (messageClasses) cnm.push(messageClasses);
const node = (<p className={cnm.flat().join(' ')}><small>
{message}
</small></p>);
return (message && node);
}
content(children = this.props.children) {
const { inline, first, placeholder, label, floating, inlineControlClasses } = this.props;
const cn = ['position-relative'];
if (inline) cn.push('d-flex');
if (placeholder && !label && floating) cn.push('form-floating');
const wrapProps = {};
const className = cn.join(' ');
const isSetPassField = placeholder === 'Repetir contraseña'
if (this.ContentWrap !== Fragment) wrapProps.className = className;
return (<>
<this.ContentWrap {...wrapProps}>
{floating && (first === 'label' && label) && this.labelNode}
{inline ? <div className={inlineControlClasses}>
{this.inputNode}
{this.errorMessageNode}
{this.messageNode}
</div> : this.inputNode}
{(floating && (first !== 'label' || (placeholder && !label)) || isSetPassField) && this.labelNode}
{!inline && <>
{this.errorMessageNode}
{this.messageNode}
</>}
</this.ContentWrap>
{children}
</>);
}
};