Search code examples
vuejs2datepickerelement-ui

How to accept shorter user input into Element UI DatePicker?


In an existing code base the ElementUI and Vue2 packages got updated. The users are relying on what looks to be unintended behavior from the outdated DatePicker UI component.

The date is visually formatted as 30/01/2022 in the input field of the DatePicker elements. To speed up typing users were entering 30012022 into the field instead. This behavior disappeared after the updates. Only entering the date with the / separator gets accepted.

How do I overwrite the vendor methods of the ElementUI Date Time component to ease the input validation?


Solution

  • Gif shows the behavior of entering partial dates

    Replacing validation can be done by wrapping the el-date-picker into your own component. The trick is to use $nextTick inside mounted() and then access the method you want to replace through a reference on the wrapped component.

    A small code example of the .vue file using TypeScript:

    <template>
      <el-date-picker
        ref="customDatePicker"
        :type="type"
        :size="size"
        :value="value"
        :clearable="clearable"
        :format="displayFormat"
        :value-format="valueFormat" />
    </template>
    
    <script lang="ts">
    import moment from 'moment'
    import { Component, Prop, Vue } from 'vue-property-decorator'
    
    type PickerType = 'year' | 'month' | 'date' | 'dates' | 'datetime' | ' week' | 'datetimerange' | 'daterange' | 'monthrange'
    type Size = 'large' | 'small' | 'mini'
    
    @Component
    export default class RexDatePicker extends Vue {
      @Prop()
      readonly value: any
    
      @Prop({ type: String, default: 'small' })
      readonly size!: Size
    
      @Prop({ type: String, default: 'date' })
      readonly type!: PickerType
    
      @Prop({ type: String, default: 'yyyy-MM-dd' })
      readonly valueFormat!: string
    
      data() {
        return {
          clearable: this.$attrs.clearable !== 'false'
        }
      }
    
      get displayFormat() : string {
        if (this.type === 'year') {
          return 'yyyy'
        }
        return 'dd/MM/yyyy'
      }
    
      mounted() {
        this.$nextTick(() => {
          var elPicker: any = this.$refs.customDatePicker
          if (this.type === 'date' || this.type === 'daterange') {
            // inject custom date input behavior
            const originalParseFunction = elPicker.parseString
            elPicker.parseString = function (value) {
              value = expandNumbersToDate(value)
              return originalParseFunction(value)
            }
          }
        })
      }
    }
    
    function expandNumbersToDate(value) {
      // expects String, Date or an Array of those two
      if (Object.prototype.toString.call(value) === '[object String]') {
        var currentMonth = moment().format('MM')
        var currentYear = moment().format('YYYY')
        value = value.replace(/^[^\d]*(\d{2})[^\d]*$/, `$1/${currentMonth}/${currentYear}`)
        value = value.replace(/^[^\d]*(\d{2})(\d{2})[^\d]*$/, `$1/$2/${currentYear}`)
        value = value.replace(/^[^\d]*(\d{2})\/?(\d{2})\/?(\d{4})[^\d]*$/, '$1/$2/$3')
      }
      if (Array.isArray(value)) {
        value = value.map(date => expandNumbersToDate(date))
      }
      return value
    }
    </script>