textarea/textarea.vue

<template>
    <div class="ion-textarea" :class="[modeClass,{'has-count': this.count > 0}]">
        <div class="input-inner-wrap" @click="setFocus($event)">
            <textarea :class="[textInputClass]"
                      class="text-input"
                      :value="inputValue"
                      :placeholder="placeholder"
                      :disabled="disabled"
                      :readonly="readonly"
                      :required="required"
                      :autofocus="autofocus"
                      :maxlength="max"
                      :rows="rows"
                      ref="textarea"
                      @keyup="inputKeyUp($event)"
                      @blur="inputBlurred($event)"
                      @focus="inputFocused($event)"
                      @input="inputChanged($event)"
                      @keydown="inputKeyDown($event)"></textarea>
            <div class="input-count" v-if="count > 0">{{inputValue.length}}/{{count}}</div>
        </div>
    </div>
</template>
<style lang="scss" src="./style.scss"></style>
<script type="text/javascript">
  /**
   * @component Textarea
   * @description
   *
   * ## 表单组件 Textarea输入框
   *
   * ### 如何引入
   * ```
   * // 引入
   * import { Textarea } from 'vimo'
   * // 安装
   * Vue.component(Textarea.name, Textarea)
   * // 或者
   * export default{
   *   components: {
   *     Textarea
   *  }
   * }
   * ```
   *
   * @props {Boolean} [showFocusHighlight]      - focus时底部高亮
   * @props {Boolean} [disabled]                - 如果为true, 用户无法输入
   * @props {Number} [maxlength]                - 设置最大值, 只对textarea有效
   * @props {Number} [rows=3]                   - 设置行数, 只对textarea有效
   * @props {Boolean} [autosize]                - 自动高度
   * @props {Boolean} [autofocus]               - 自动对焦
   * @props {String} [mode='ios']               - 当前平台
   * @props {String} [placeholder]              - 占位文字
   * @props {Boolean} [readonly]                - 只读模式, 不能修改
   * @props {Boolean} [count]                   - 计数模式
   * @props {*} [value]                         - 内容输入值
   *
   * @fires component:Textarea#onBlur
   * @fires component:Textarea#onFocus
   * @fires component:Textarea#onInput
   * @fires component:Textarea#onKeyup
   * @fires component:Textarea#onKeydown
   * @fires component:Textarea#onValid
   * @fires component:Textarea#onInvalid
   *
   * @demo #/textarea
   * @usage
   * <Textarea placeholder="Text Textarea">
   * <Textarea @onBlur="blur($event)" @onFocus="focus($event)" @onInput="onInput($event)" placeholder="Enter a description"></Textarea>
   * */
  import { hasFocus, setElementClass } from '../../util/util'
  import { isPresent, isNumber } from '../../util/type'
  import Autosize from 'autosize'

  export default {
    name: 'Textarea',
    inject: {
      itemComponent: {
        from: 'itemComponent',
        default: null
      }
    },
    data () {
      return {
        max: this.maxlength,
        isValid: false, // 验证结果
        inputValue: this.value || '' // 内部value值
      }
    },
    props: {
      /**
       * focus时, 下划线是否高亮
       * */
      showFocusHighlight: Boolean,

      /**
       * 如果为true, 用户无法输入
       * */
      disabled: Boolean,

      /**
       * 设置最大值, 只对textarea有效
       * */
      maxlength: Number,

      /**
       * 设置行数, 只对textarea有效
       * */
      rows: {
        type: Number,
        default: 3
      },

      /**
       * 自动focus
       * */
      autofocus: Boolean,

      /**
       * 当前平台
       * */
      mode: {
        type: String,
        default () { return this.$config && this.$config.get('mode', 'ios') || 'ios' }
      },

      placeholder: String,

      /**
       * 只读模式, 不能修改
       * */
      readonly: Boolean,

      /**
       * 自动高度
       * */
      autosize: Boolean,

      /**
       * 必填
       * */
      required: Boolean,

      /**
       * 计数模式
       * */
      count: Number,

      /**
       * 内容输入值
       * */
      value: [String, Number, Object, Function]
    },
    watch: {
      value (val) {
        this.inputValue = val
      }
    },
    computed: {
      modeClass () {
        return `input-${this.mode}`
      },
      textInputClass () {
        return `text-input-${this.mode}`
      },
      textareaElement () {
        return this.$refs.textarea
      },
      hasValue () {
        const inputValue = this.inputValue
        return (inputValue !== null && inputValue !== undefined && inputValue !== '')
      }
    },
    methods: {

      // -------- public --------
      /**
       * @function update
       * @description
       * 更新textarea组件
       * */
      update () {
        Autosize && Autosize.update(this.textareaElement)
      },
      /**
       * @function destroy
       * @description
       * 销毁textarea组件
       * */
      destroy () {
        Autosize && Autosize.destroy(this.textareaElement)
      },

      // -------- private --------
      /**
       * 对外传递onKeyup事件
       * @private
       * */
      inputKeyUp ($event) {
        /**
         * @event  component:Textarea#onKeyup
         * @description onKeyup
         * @property {object} $event - 事件对象
         */
        this.$emit('onKeyup', $event)
      },

      /**
       * 键盘按下事件
       * @private
       * */
      inputKeyDown ($event) {
        /**
         * @event  component:Textarea#onKeydown
         * @description onKeydown
         * @property {object} $event - 事件对象
         */
        this.$emit('onKeydown', $event)
      },

      /**
       * 设置当前组件为focus状态
       * */
      setFocus () {
        if (!hasFocus(this.textareaElement)) {
          this.textareaElement.focus()
        }
      },

      /**
       * 监听并发送blur事件
       * @private
       */
      inputBlurred () {
        // 向父组件Item添加标记
        this.setItemHasFocusClass(false)

        /**
         * @event  component:Textarea#onBlur
         * @description blur事件
         */
        this.$emit('onBlur')

        // 验证输入结果
        this.verification()
      },

      /**
       * 必填验证
       *
       * */
      verification () {
        if (!this.required) return

        this.isValid = this.hasValue
        if (this.isValid) {
          /**
           * @event  component:Textarea#onValid
           * @description 验证通过
           * @property {*} value - 当前检查的value
           */
          this.$emit('onValid', this.inputValue)
          this.itemComponent && setElementClass(this.itemComponent.$el, 'ng-valid', true)
          this.itemComponent && setElementClass(this.itemComponent.$el, 'ng-invalid', false)
        } else {
          /**
           * @event  component:Textarea#onInvalid
           * @description 验证失败
           * @property {*} value - 当前检查的value
           */
          this.$emit('onInvalid', this.inputValue)
          this.itemComponent && setElementClass(this.itemComponent.$el, 'ng-valid', false)
          this.itemComponent && setElementClass(this.itemComponent.$el, 'ng-invalid', true)
        }
      },

      /**
       * 监听并发送focus事件
       * @private
       */
      inputFocused () {
        // 向父组件Item添加标记
        this.setItemHasFocusClass(true)
        this.setFocus()
        /**
         * @event  component:Textarea#onFocus
         * @description focus事件
         */
        this.$emit('onFocus')
        this.itemComponent && setElementClass(this.itemComponent.$el, 'ng-touched', true)
      },

      /**
       * 监听input事件, 更新input的value(inputValue)
       * @private
       */
      inputChanged ($event) {
        this.inputValue = $event && $event.target ? $event.target.value : ''
        this.setItemHasValueClass()

        /**
         * @event  component:Textarea#onInput
         * @description input事件
         * @property {*} value - 输入值
         */
        this.$emit('input', this.inputValue)
        this.$emit('onInput', this.inputValue)
      },

      /**
       *  设置父组件Item被点中时的class
       *  @private
       */
      setItemHasFocusClass (isFocus) {
        if (this.itemComponent) {
          setElementClass(this.itemComponent.$el, 'input-has-focus', isFocus)
        }
        setElementClass(this.$el, 'input-has-focus', isFocus)
      },

      /**
       *  设置父组件Item有值时的class
       *  @private
       */
      setItemHasValueClass () {
        if (this.itemComponent) {
          setElementClass(this.itemComponent.$el, 'input-has-value', this.hasValue)
        }
        setElementClass(this.$el, 'input-has-value', this.hasValue)
      }
    },
    created () {
      if (isPresent(this.count) && isNumber(this.count)) {
        this.max = this.count
      }
    },
    mounted () {
      if (this.autosize) {
        Autosize(this.textareaElement)
      }

      // 找到外部item实例
      if (this.itemComponent) {
        setElementClass(this.itemComponent.$el, 'item-textarea', true)
        setElementClass(this.itemComponent.$el, 'item-input', true)
        setElementClass(this.itemComponent.$el, 'show-focus-highlight', this.showFocusHighlight)
        setElementClass(this.itemComponent.$el, 'show-valid-highlight', this.required)
        setElementClass(this.itemComponent.$el, 'show-invalid-highlight', this.required)
      }

      // 初始化时,判断是否有value
      this.setItemHasValueClass()
    }
  }
</script>