import * as React from 'react'; import {debounce as db, isArray, isEmpty, keys, template, templateSettings, trim,} from 'lodash'; import FormComponent from '../FormComponent'; import OutsideClickHandler from 'react-outside-click-handler'; import classNames from 'classnames'; import {getId, nullToString, searchDataFetch,} from '../function'; import RenderHtml from '../renderFunction'; import PropsTypes from './types'; import * as IMask from 'imask'; import Icon from '../Icon' import * as JsSearch from 'js-search'; import Result from '../Result' import InputDropdown from "../InputDropdown"; let jsoptions = new JsSearch.Search('id'); templateSettings.interpolate = /{{([\s\S]+?)}}/g; interface State extends PropsTypes { focus: boolean, ddMenuIsActive: boolean, uuid: string, dropdownValue: any, listData: any[], selectedData: any[], maskOptions: any, passView: boolean, imask: any, isActiveComponent: boolean, } function getDropdownValue(dropdown: any) { return dropdown && dropdown.data.filter((item: any) => item[dropdown.displayValue] === dropdown.selected)[0] } function debounceTime(debounce: number) { return debounce ? debounce : 0 } function addDocumentsForSearch(data: any) { if (data && data.data && isArray(data.data) && data.data.length) { keys(data.data[0]).forEach(c => { jsoptions.addIndex(c) }); data.data.forEach((k: any) => { jsoptions.addDocuments([k]) }) } else { jsoptions.addDocuments([]) } } function setDocumentIndex(data: any) { if (data && data.length) { keys(data[0]).forEach(c => { jsoptions.addIndex(c) }); jsoptions.addDocuments(data) } } function initialMask(maskOptions: any, element: any) { if (maskOptions && !isEmpty(maskOptions)) { return new IMask(element, maskOptions); } return false } export default class Input extends FormComponent { public state: State; private input = React.createRef(); constructor(props: PropsTypes, {}) { super(props); const uuid = getId(props.id, this.defaultId); const {search, value, dropdown, mask, debounce, type} = props; if (search) { jsoptions = new JsSearch.Search(this.getDisplayItem('displayValue')); } if (search && !isEmpty(search)) { addDocumentsForSearch(search); } this.state = { focus: false, ddMenuIsActive: false, listData: [], selectedData: [], uuid: uuid, value: nullToString(value), dropdownValue: getDropdownValue(dropdown), maskOptions: mask && !isEmpty(mask) ? {mask: String(mask)} : {}, imask: null, autoFocus: false, passView: false, isActiveComponent: false, type: type }; this.debounce = db(this.debounce, debounceTime(debounce || 0)); } async componentDidMount() { const { value, maskOptions } = this.state; const { autoFocus } = this.props; this.setState({ size: this.size(), readOnly: this.readOnly(), }); await this.setMask(maskOptions.mask || ''); if (autoFocus) { await this.focus(); } this.validate(value); this.props.element && this.props.element(this.input); } setMask(newMask?: any) { const { imask } = this.state; const mOptions = { mask: newMask }; if (!newMask) { return; } if (imask) { imask.destroy(); } const element = this.input.current; const masked = initialMask(mOptions, element); this.setState({ imask: masked, value: masked._value }, () => { masked.updateValue(); }) } updateMask(newMask?: any) { const { imask } = this.state; if (!newMask && imask) { this.setState({ value: imask._unmaskedValue }) } else if (newMask) { this.setMask(newMask); } } focus() { const {value} = this.state; const {search} = this.props; this.input.current && this.input.current.focus(); if (value && search) { this.debounce(value) } this.setState({ focus: true, isActiveComponent: true }) } debounce(val: string) { const {selectedData} = this.state; const {search} = this.props; if (search && !isEmpty(search) && val) { if (search.remote && !isEmpty(search.remote)) { let url: any = search && template(search.remote); url = url({key: val}); searchDataFetch(url, val, (callback: any) => { setDocumentIndex(callback.data); this.setState({ listData: val ? callback.data : selectedData, loading: callback.loading }, () => { this.dataOnReady() }) }) } if (search.data && !isEmpty(search.data)) { let filterData: any[] = []; filterData = jsoptions.search(val); this.setState({ listData: trim(val) ? filterData : search.data }, () => { this.dataOnReady() }); } } else { this.setState({ listData: [], loading: false }, () => { this.dataOnReady() }); } } componentWillReceiveProps(nextProps: any) { if (nextProps.value !== this.props.value) { let newValue = nullToString(nextProps.value); this.setState({ value: newValue }, () => { this.setInputValue() }) } if (nextProps.mask !== this.props.mask) { this.updateMask(nextProps.mask) } if (nextProps.dropdown !== this.props.dropdown) { this.setState({ dropdownValue: getDropdownValue(nextProps.dropdown) }) } if (nextProps.search !== this.props.search) { if (nextProps.search && !isEmpty(nextProps.search)) { addDocumentsForSearch(nextProps.search); } } } dataOnReady() { this.props.onChange && this.props.onChange(this.getInputData()['output']) } handleChange(e: any) { const { imask } = this.state; let inputValue: string = e.target.value; if (imask) { imask.updateValue(); } this.setState({ value: inputValue }, () => { if (imask) { imask.updateValue(); } this.debounce(inputValue); this.validate(inputValue); }) } handleBlur = () => { this.props.onBlur && this.props.onBlur(this.getInputData()['output']); }; handleClick = () => { this.props.onClick && this.props.onClick() }; handlePress = (e: any) => { this.props.onKeyPress && this.props.onKeyPress(e) }; handleDown = (e: any) => { this.props.onKeyDown && this.props.onKeyDown(e) }; setInputValue() { let val: any = this.getInputData()['value']; this.setState({ value: val, }, () => { this.validate(val); this.dataOnReady() }) } async dataSelectItem(val: any) { const {selectedData} = this.state; const {search} = this.props; let afterData: any = {}; let tempselectedData: any[] = selectedData; await (() => { if (search && !isEmpty(search)) { tempselectedData = []; val && tempselectedData.push(val); afterData = { selectedData: tempselectedData, listData: [] } } })(); this.setState({ ...afterData }, () => { this.setInputValue(); }) } getInputData() { const {selectedData, value, dropdownValue, /* maskOptions */} = this.state; const {dropdown} = this.props; let result: Object = {}; let dropdownData: Object = {}; let output: any = null; let str: string | undefined = value; if (selectedData && selectedData.length) { str = selectedData[0][this.getDisplayItem('displayField')] } output = str; if (dropdown && !isEmpty(dropdown)) { dropdownData = { dropdown: dropdownValue[dropdown.displayValue] }; output = { value: str, ...dropdownData } } result = { value: str, output }; return result } removeAll() { this.setState({ selectedData: [], listData: [], value: '' }) } setDropdownValue(val: any) { this.setState({ dropdownValue: val, ddMenuIsActive: false }, () => { this.dataOnReady() }) } renderDropDown(): JSX.Element | null { const {ddMenuIsActive, dropdownValue} = this.state; const {dropdown, disabled} = this.props; if (dropdown && !isEmpty(dropdown)) { return
} return null } renderPasswordView() { const {passView} = this.state; if (this.props.type === 'password' && this.props.passwordView) { return this.setState({passView: !passView})} style={{pointerEvents: 'auto', cursor: 'pointer'}}> } return null } outsideClick() { const {isActiveComponent} = this.state; if (isActiveComponent) { this.setState({ focus: false, listData: [], selectedData: [], ddMenuIsActive: false, isActiveComponent: false, }) } } renderResult() { const {listData, selectedData} = this.state; if (listData && listData.length) { return this.dataSelectItem(val)} onRemoveAll={() => this.removeAll()} /> } return null } renderInputDropdown() { const {customRender, inputDropdown} = this.props; const {listData} = this.state; if (customRender) { return {customRender()} } if (listData && listData.length) { return {this.renderResult()} } return null } public render(): JSX.Element { const {value, uuid, passView, listData} = this.state; const {autoComplete, spellCheck, color, disabled, readOnlyInput, maxLength, icon, customStyle, rounded, loading, label, dropdown, infoText, button, caption, /* customStyle, label, , button, infoText, caption,*/ type, passwordView, noBorder, noRadius, search} = this.props; const uiClass = classNames(`krax-input ${this.size()} is-${color || 'default'} ${customStyle}`, { 'is-rounded': rounded, 'is-danger': this.error(), 'is-focused': this.error(), 'is-bottom-radius-none': listData.length, 'no-border': noBorder, 'no-radius': noRadius, }), containerClass = classNames(`krax-control is-expanded ${this.size()}`, { 'has-icons-left': this.iconLeft(), 'has-icons-right': this.iconRight() || passwordView, 'is-loading': loading, 'is-data': value }), fieldClass = classNames(`krax-field`, { 'has-addons': label || dropdown || button, }), captionClass = classNames('caption-text-ui', { 'is-data': value }); let t = type; if (passwordView && passView) { t = 'text' } else if (passwordView && !passView) { t = 'password' } return ( { this.outsideClick() }}>
this.focus()}>
{RenderHtml.renderLabel(label, 'left', this.size())}
this.handleChange(val)} onBlur={() => this.handleBlur()} // onFocus={() => this.focus()} onClick={() => this.handleClick()} onKeyPress={(e) => { this.handlePress(e) }} onKeyDown={(e) => this.handleDown(e)} onKeyUp={(e) => !search && this.handleChange(e)} spellCheck={spellCheck} disabled={disabled} readOnly={this.readOnly() || readOnlyInput} maxLength={maxLength} ref={this.input} tabIndex={0} autoCorrect={'off'} />
{caption}
{/*{this.renderResult()}*/} {this.renderInputDropdown()} {RenderHtml.renderIcon(icon, 'left', this.size())} {!loading ? RenderHtml.renderIcon(icon, 'right', this.size()) : null} {this.renderPasswordView()}
{RenderHtml.renderLabel(label, 'right', this.size())} {this.renderDropDown()} {button ?
{button()}
: null}
{RenderHtml.renderError(this.error())} {isEmpty(this.error()) ?
{infoText}
: null}
); } getDisplayItem(type: string) { const {search} = this.props; if (search && search[type] && !isEmpty(search[type])) { return search && search[type]; } return false } }