Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@skava/forms / src / new-forms / inputs / InputState.ts
Size: Mime:
import { action, observable, computed } from 'xmobx/mobx'
import { CommonState } from '../CommonState'
import { DEFAULT_VALIDATOR, DEFAULT_SERIALIZER, toType } from '../deps'
import {
  AnyObj,
  ValidatorFunction,
  SerializerFunction,
  Type,
  InputStateStore,
} from './typings'
// @note @circular
import { FormState } from '../forms/FormState'

let identifierIndex = 0
export class InputState<InputProps = AnyObj> extends CommonState {
  @observable
  identifier: string = (identifierIndex += 1) + '-input'

  /**
   * @description this is a type that Plugins could extend
   * @todo this should have a generic
   */
  @observable
  type: Type = 'text'

  /** @description these are the props for the ObserverInput/plugin */
  @observable.shallow
  attributes: Partial<InputProps> = {}

  @observable.ref
  validator: ValidatorFunction = DEFAULT_VALIDATOR

  @observable.ref
  serializer: SerializerFunction = DEFAULT_SERIALIZER

  /** @description parent form - we could use `context` instead */
  @observable.ref
  formState?: FormState

  /** @description this is used for serializing */
  @observable
  propertyName: string = ''

  /** @todo typings */
  store: InputStateStore = observable.map()

  /**
   * using this to check if an input value has been changed
   * this gets reset when the state is `reset`
   */
  @computed
  get isDirty(): boolean {
    if (this.store.has('isDirty')) {
      return this.store.get('isDirty')
    } else {
      return this.value !== '' && this.value !== undefined
    }
  }

  @action
  setIsDirty(isDirty: boolean): void {
    this.store.set('isDirty', isDirty)
  }

  /** should not use this public-ally or at all with context */
  @action
  setFormState(formState: FormState) {
    this.formState = formState
  }

  @action
  setType(type: Type) {
    /**
     * @todo need to use `toType` too to detect which type
     * @example this.type = toType(type)
     */
    this.type = type
  }

  @action
  setValidator(validator: ValidatorFunction) {
    this.validator = validator
  }

  @action
  setSerializer(serializer: SerializerFunction) {
    this.serializer = serializer
  }

  /**
   * @description this is computed only when changed
   * @see https://mobx.js.org/refguide/computed-decorator.html
   *
   * @todo may want to trigger validation at different points
   * @example will cover in stories
   *
   * @todo this seems flawed at its core
   *
   * 1. we could `set` isValid
   * 2. we could `inherit` showing isValid
   *    since we would only show invalid inputs when the whole form shows them?
   *
   * @todo at very least, pass in just `this`...
   *       we would want just `this.value`...
   *
   * @event blur
   * - in this case, we validate on blur
   * - strategy: this will not work
   *
   * @event submit
   * - in this case, we validate on submit only
   * - strategy: this will not work
   *
   * @event onChange
   * - in this case, we validate on every keystroke or change
   * - strategy: this computed value works
   */
  @computed
  get isValid() {
    if (this.store.has('isValid') === true) {
      return this.store.get('isValid')
    } else {
      return this.validator(this.value) === true
    }
  }
  @action
  setIsValid(isValid: boolean) {
    this.store.set('isValid', isValid)
  }

  /**
   * @description this in the initial POC of minimalism was
   *              `return string if error, true if valid`
   * @todo may want to change
   */
  @computed
  get errorText() {
    return this.validator(this.value) as string
  }

  @action
  setAttribute(key: string, value: any) {
    this.attributes[key] = value
  }

  @action
  setPropertyName(named: string) {
    this.propertyName = named
  }

  @action
  setIdentifier(identifier: string) {
    this.identifier = identifier
  }

  toJSON() {
    return this.serializer(this)
  }

  merge(obj: AnyObj) {
    Object.keys(obj).forEach(key => {
      const value = obj[key]

      switch (key) {
        case 'propertyName':
        case 'name':
          return this.setPropertyName(value)
        case 'value':
          return this.setValue(value)
        case 'identifier':
          return this.setIdentifier(value)
        case 'label':
          return this.setLabel(value)
        case 'type':
          return this.setType(value)
        case 'validator':
          return this.setValidator(value)
        case 'serializer':
          return this.setSerializer(value)
        case 'formState':
          return this.setFormState(value)
        case 'isActive':
          return this.setIsActive(value)
        case 'isSelected':
          return this.setIsSelected(value)
        case 'isDisabled':
          return this.setIsDisabled(value)
        // isRequired
        case 'attributes':
          // or could curry the method
          const setAttribute = (attribute: string) =>
            this.setAttribute(attribute, value)
          return Object.keys(value).forEach(setAttribute)
        default:
          return this.setAttribute(key, value)
      }
    })
  }
  static from(obj: AnyObj) {
    const state = new InputState()
    state.merge(obj)
    return state
  }
}