AJ Jones
AJ Jones

Reputation: 55

How to make a Vue.js Month drop down list?

This works correctly in my test project but I do not know exactly how this is being done. In other words I do not understand how this is being built with Vue.js. It seems a little complicated way of doing things. I feel that this can be simplified. Any suggestions to help simply this would be great.

Basically, I want to create a list of months from 01 - 12 for a credit card expiration.

< script >
  export default {
    methods: {
      minCardMonth() {
        if (this.cardYear === this.minCardYear) return new Date().getMonth() + 1;
        return 1;
      }
    }
  } <
  /script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>


<select class="input-control">
  <option disabled selected>-- Month --</option>
  <option v-bind:value="n < 10 ? '0' + n : n" v-for="n in 12" v-bind:disabled="n < minCardMonth" v-bind:key="n">
    {{n
    < 10 ? '0' + n : n}} </option>
</select>

Upvotes: 0

Views: 1349

Answers (1)

muka.gergely
muka.gergely

Reputation: 8329

Here's a snippet that's a bit more advanced: it also handles the edge case when a user selects a future year and a month that's already past this year, then changes back to the current year (the month select defaults to the original value).

The whole point is: sometimes it seems easy to control the UI (<template> parts) of a component - but it's always better to decouple the visuals from the data. Try organizing your code so that the presentation/UI reacts to changes in the underlying data, and control the data.

In this case, you may be right, maybe this snippet is a bit overkill - but to be honest, I wouldn't do it otherwise for myself (maybe add TypeScript :) )

// generating two digits months numbers
const MONTHS_OF_YEAR = () => {
  return Array(12)
    .fill(0)
    .map((_, i) => {
      return {
        value: i,
        text: ("0" + (i + 1)).slice(-2),
        disabled: false,
      }
    })
}

// the default (unselectable) value in
// the months options:
const DEFAULT_MONTH_VALUE = () => ({
  text: "--- Month ---",
  value: null,
  disabled: true,
})

const DEFAULT_YEAR_VALUE = () => [
  "--- Year ---",
  2021,
  2022,
  2023,
  2024,
  2025,
]

new Vue({
  el: "#app",
  data() {
    return {
      cardYear: DEFAULT_YEAR_VALUE()[0],
      cardMonth: DEFAULT_MONTH_VALUE(),
      cardYearOptions: DEFAULT_YEAR_VALUE(),
      cardMonthOptions: MONTHS_OF_YEAR(),
    }
  },
  computed: {
    minCardYear() {
      return new Date().getFullYear()
    },
    minCardMonth() {
      return new Date().getMonth()
    },
    possibleMonths() {
      // defaulting to "all months are available"
      let returnMonths = [...this.cardMonthOptions]

      if (this.cardYear === this.minCardYear) {
        returnMonths = this.cardMonthOptions
          .map((month, i) => {
            if (i < this.minCardMonth) {
              return {
                ...month,
                disabled: true,
              }
            }
            return month
          })
      }

      // returning the array that will
      // be iterated over - its first
      // value is always the default
      return [
        DEFAULT_MONTH_VALUE(),
        ...returnMonths,
      ]
    },
  },
  watch: {
    // if cardYear is changed from a future date
    // to the current year, it's wise to check
    // selected month - maybe we're past that month
    cardYear(newVal) {
      const selectedMonth = this.possibleMonths.find(month => {
        return month.value === this.cardMonth.value
      })
      if (selectedMonth?.disabled) {
        this.cardMonth = DEFAULT_MONTH_VALUE()
      }
    },
  },
  template: `
    <div>
      MinCardYear: {{ minCardYear }}<br />
      SelectedCardYear: {{ cardYear }}<br />
      SelectedCardMonth: {{ cardMonth.text }}<br />
      <select v-model="cardYear">
        <option
          v-for="(year, i) in cardYearOptions"
          :key="year"
          :value="year"
          :disabled="i === 0"
        >
          {{ year }}
        </option>
      </select>
      <select v-model="cardMonth">
        <option
          v-for="month in possibleMonths"
          :key="month.value"
          :value="month"
          :disabled="month.disabled"
        >
          {{ month.text }}
        </option>
      </select>
    </div>
  `
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Upvotes: 1

Related Questions