Advertisement
Guest User

OOTB TextField

a guest
Aug 25th, 2016
294
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import * as React from 'react';
  2. import { ITextFieldProps } from './TextField.Props';
  3. import { Label } from '../../Label';
  4. import { css } from '../../utilities/css';
  5. import { Async } from '../../utilities/Async/Async';
  6. import { getId } from '../../utilities/object';
  7. import './TextField.scss';
  8.  
  9. export interface ITextFieldState {
  10.   value?: string;
  11.  
  12.   /** Is true when the control has focus. */
  13.   isFocused?: boolean;
  14.  
  15.   /**
  16.    * The validation error message.
  17.    *
  18.    * - If there is no validation error or we have not validated the input value, errorMessage is an empty string.
  19.    * - If we have done the validation and there is validation error, errorMessage is the validation error message.
  20.    */
  21.   errorMessage?: string;
  22. }
  23.  
  24. export class TextField extends React.Component<ITextFieldProps, ITextFieldState> {
  25.   public static defaultProps: ITextFieldProps = {
  26.     multiline: false,
  27.     resizable: true,
  28.     underlined: false,
  29.     onChanged: () => { /* noop */ },
  30.     onBeforeChange: () => { /* noop */ },
  31.     onNotifyValidationResult: () => { /* noop */ },
  32.     onGetErrorMessage: () => undefined,
  33.     deferredValidationTime: 200,
  34.     errorMessage: ''
  35.   };
  36.  
  37.   private _id: string;
  38.   private _descriptionId: string;
  39.   private _async: Async;
  40.   private _delayedValidate: (value: string) => void;
  41.   private _isMounted: boolean;
  42.   private _lastValidation: number;
  43.   private _latestValidateValue;
  44.   private _willMountTriggerValidation;
  45.   private _field;
  46.  
  47.   public constructor(props: ITextFieldProps) {
  48.     super(props);
  49.  
  50.     this._id = getId('TextField');
  51.     this._descriptionId = getId('TextFieldDescription');
  52.     this._async = new Async(this);
  53.  
  54.     this.state = {
  55.       value: props.value || props.defaultValue,
  56.       isFocused: false,
  57.       errorMessage: ''
  58.     };
  59.  
  60.     this._onInputChange = this._onInputChange.bind(this);
  61.     this._onFocus = this._onFocus.bind(this);
  62.     this._onBlur = this._onBlur.bind(this);
  63.  
  64.     this._delayedValidate = this._async.debounce(this._validate, this.props.deferredValidationTime);
  65.     this._lastValidation = 0;
  66.     this._latestValidateValue = '';
  67.     this._willMountTriggerValidation = false;
  68.   }
  69.  
  70.   /**
  71.    * Gets the current value of the text field.
  72.    */
  73.   public get value(): string {
  74.     return this.state.value;
  75.   }
  76.  
  77.   public componentWillMount() {
  78.     this._willMountTriggerValidation = true;
  79.     this._validate(this.state.value);
  80.   }
  81.  
  82.   public componentDidMount() {
  83.     this._isMounted = true;
  84.   }
  85.  
  86.   public componentWillReceiveProps(newProps: ITextFieldProps) {
  87.     const { onBeforeChange } = this.props;
  88.  
  89.     if (newProps.value !== undefined && newProps.value !== this.state.value) {
  90.       if (onBeforeChange) {
  91.         onBeforeChange(newProps.value);
  92.       }
  93.  
  94.       this.setState({
  95.         value: newProps.value,
  96.         errorMessage: ''
  97.       } as ITextFieldState);
  98.  
  99.       this._delayedValidate(newProps.value);
  100.     }
  101.   }
  102.  
  103.   public componentWillUnmount() {
  104.     this._async.dispose();
  105.     this._isMounted = false;
  106.   }
  107.  
  108.   public render() {
  109.     let { disabled, required, multiline, underlined, label, description, iconClass, className } = this.props;
  110.     let { isFocused } = this.state;
  111.     const errorMessage: string = this._errorMessage;
  112.  
  113.     const textFieldClassName = css('ms-TextField', className, {
  114.       'is-required': required,
  115.       'is-disabled': disabled,
  116.       'is-active': isFocused,
  117.       'ms-TextField--multiline': multiline,
  118.       'ms-TextField--underlined': underlined
  119.     });
  120.  
  121.     return (
  122.       <div className={ textFieldClassName }>
  123.         { label && <Label htmlFor={ this._id }>{ label }</Label> }
  124.         { iconClass && <i className={ iconClass }></i> }
  125.         { multiline ? this._renderTextArea() : this._renderInput() }
  126.         { errorMessage && <div aria-live='assertive' className='ms-u-screenReaderOnly' data-automation-id='error-message'>{ errorMessage }</div> }
  127.         { (description || errorMessage) &&
  128.           <span id={ this._descriptionId }>
  129.             { description && <span className='ms-TextField-description'>{ description }</span> }
  130.             { errorMessage && <p className='ms-TextField-errorMessage ms-u-slideDownIn20'>{ errorMessage }</p> }
  131.           </span>
  132.         }
  133.       </div>
  134.     );
  135.   }
  136.  
  137.   /**
  138.    * Sets focus on the text field
  139.    */
  140.   public focus() {
  141.     if (this._field) {
  142.       this._field.focus();
  143.     }
  144.   }
  145.  
  146.   /**
  147.    * Selects the text field
  148.    */
  149.   public select() {
  150.     if (this._field) {
  151.       this._field.select();
  152.     }
  153.   }
  154.  
  155.   /**
  156.    * Sets the selection start of the text field to a specified value
  157.    */
  158.   public setSelectionStart(value: number) {
  159.     if (this._field) {
  160.       this._field.selectionStart = value;
  161.     }
  162.   }
  163.  
  164.   /**
  165.    * Sets the selection end of the text field to a specified value
  166.    */
  167.   public setSelectionEnd(value: number) {
  168.     if (this._field) {
  169.       this._field.selectionEnd = value;
  170.     }
  171.   }
  172.  
  173.   private _onFocus(ev: React.FocusEvent) {
  174.     if (this.props.onFocus) {
  175.       this.props.onFocus(ev);
  176.     }
  177.  
  178.     this.setState({ isFocused: true });
  179.   }
  180.  
  181.   private _onBlur(ev: React.FocusEvent) {
  182.     if (this.props.onBlur) {
  183.       this.props.onBlur(ev);
  184.     }
  185.  
  186.     this.setState({ isFocused: false });
  187.   }
  188.  
  189.   private get _fieldClassName(): string {
  190.     const errorMessage: string = this._errorMessage;
  191.     let textFieldClassName: string;
  192.  
  193.     if (this.props.multiline && !this.props.resizable) {
  194.       textFieldClassName = 'ms-TextField-field ms-TextField-field--unresizable';
  195.     } else {
  196.       textFieldClassName = 'ms-TextField-field';
  197.     }
  198.  
  199.     return css(textFieldClassName, {
  200.       'ms-TextField-invalid': !!errorMessage
  201.     });
  202.   }
  203.  
  204.   private get _errorMessage(): string {
  205.     let { errorMessage } = this.state;
  206.     if (!errorMessage) {
  207.       errorMessage = this.props.errorMessage;
  208.     }
  209.  
  210.     return errorMessage;
  211.   }
  212.  
  213.   private _renderTextArea(): React.ReactElement<React.HTMLProps<HTMLAreaElement>> {
  214.     return (
  215.       <textarea
  216.         { ...this.props }
  217.         id={ this._id }
  218.         ref={ (c): HTMLTextAreaElement => this._field = c }
  219.         value={ this.state.value }
  220.         onChange={ this._onInputChange }
  221.         className={ this._fieldClassName }
  222.         aria-label={ this.props.ariaLabel }
  223.         aria-describedby={ this._descriptionId }
  224.         aria-invalid={ !!this.state.errorMessage }
  225.         onFocus={ this._onFocus }
  226.         onBlur={ this._onBlur }
  227.         />
  228.     );
  229.   }
  230.  
  231.   private _renderInput(): React.ReactElement<React.HTMLProps<HTMLInputElement>> {
  232.     return (
  233.       <input
  234.         { ...this.props }
  235.         id={ this._id }
  236.         type='text'
  237.         ref={ (c): HTMLInputElement => this._field = c }
  238.         value={ this.state.value }
  239.         onChange={ this._onInputChange }
  240.         className={ this._fieldClassName }
  241.         aria-label={ this.props.ariaLabel }
  242.         aria-describedby={ this._descriptionId }
  243.         aria-invalid={ !!this.state.errorMessage }
  244.         onFocus={ this._onFocus }
  245.         onBlur={ this._onBlur }
  246.         />
  247.     );
  248.   }
  249.  
  250.   private _onInputChange(event: React.KeyboardEvent): void {
  251.     const element: HTMLInputElement = event.target as HTMLInputElement;
  252.     const value: string = element.value;
  253.  
  254.     this.setState({
  255.       value: value,
  256.       errorMessage: ''
  257.     } as ITextFieldState);
  258.     this._willMountTriggerValidation = false;
  259.     this._delayedValidate(value);
  260.     const { onBeforeChange } = this.props;
  261.     onBeforeChange(value);
  262.   }
  263.  
  264.   private _validate(value: string): void {
  265.     // In case of _validate called multi-times during executing validate logic with promise return.
  266.     if (this._latestValidateValue === value) {
  267.       return;
  268.     }
  269.  
  270.     this._latestValidateValue = value;
  271.     let { onGetErrorMessage } = this.props;
  272.     let result: string | PromiseLike<string> = onGetErrorMessage(value || '');
  273.  
  274.     if (result !== undefined) {
  275.       if (typeof result === 'string') {
  276.         this.setState({
  277.           errorMessage: result
  278.         } as ITextFieldState);
  279.         this._notifyAfterValidate(value, result);
  280.       } else {
  281.         let currentValidation: number = ++this._lastValidation;
  282.  
  283.         result.then((errorMessage: string) => {
  284.           if (this._isMounted && currentValidation === this._lastValidation) {
  285.             this.setState({ errorMessage } as ITextFieldState);
  286.           }
  287.           this._notifyAfterValidate(value, errorMessage);
  288.         });
  289.       }
  290.     } else {
  291.       this._notifyAfterValidate(value, '');
  292.     }
  293.   }
  294.  
  295.   private _notifyAfterValidate(value: string, errorMessage: string): void {
  296.     if (!this._willMountTriggerValidation && value === this.state.value) {
  297.       const { onNotifyValidationResult } = this.props;
  298.       onNotifyValidationResult(errorMessage, value);
  299.       if (!errorMessage) {
  300.         const { onChanged } = this.props;
  301.         onChanged(value);
  302.       }
  303.     } else {
  304.       this._willMountTriggerValidation = false;
  305.     }
  306.   }
  307. }
  308.  
  309.  
  310.  
  311. /** WEBPACK FOOTER **
  312.  ** /src/components/TextField/TextField.tsx
  313.  **/
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement