SoFunction
Updated on 2025-04-05

Example of writing a datepicker using Vue

Preface

Writing plug-ins is very interesting and also very training people, because you can find many details in the process. In the process of front-end development, jQuery is undoubtedly an important milestone. There are also many excellent plug-ins that can be used directly around this excellent project, which greatly saves developers time. The most important function of jQuery is to cross-browser. Although the browser market is not perfect now, it is no longer as miserable as before. The idea of ​​data-driven views is very popular. Everyone has started to use front-end frameworks to replace jQuery. I personally like it, so I want to try to write a component.

In order to publish it to npm, the project address was changed, but the internal code was not changed, so the usage method was more convenient than before.

GitHub address:Here

Functions & Expectations

This datepicker currently only implements some commonly used functions:

  1. Choose time (this is a bit redundant)
  2. Maximum/minimum time limit
  3. Chinese/English switching (actually, only week and month need to be switched)
  4. Can be used in .vue form or directly in a browser environment
  5. It's gone. . .

Directory structure

The first step in everything is to create a project, it is just a single component, the structure is not complicated, it is the most important component file. dist is the output folder of webpack, the entry file packaged by webpack, and finally the configuration file of webpack, which is used to package our library files. So the project structure is like this:

.
├── 
├── LICENSE
├── 
├── dist
│ └── 
├── 
├── 
└── 

Start with

Writing Vue components in the .vue way is a special way of writing. Each Vue file includes three parts: template, script, and style. It is best not to become a fragment instance, so the outermost layer is first equipped with a div and used as the root element of the entire component. A datepicker is generally composed of two parts, an input box for displaying dates, and a panel for selecting dates. Because I found that input will automatically evoke the keyboard on the mobile terminal, I did not use input, and directly used div to simulate it, and the panel's visible and hidden information is determined by clicking events. The value is the final result and needs to communicate with the parent component, so the value is written as prop, and use ="xxx" in the parent component. The datepicker value is bidirectionally bound to the parent component's xxx.

<template>
 <div class="date-picker">
  <div class="input" v-text="value" @click="panelState = !panelState">
 </div>
 <div class="date-panel" v-show="panelState">
 </div>
</template>

<scrip>
 export default {
  data () {
   return {
    panelState: false //Initial value, default panel is closed   }
  },
  props: {
   value: String
  }
 }
</script>

Render date list

A month is at least 28 days. If Sunday is ranked at the beginning, then at least 4 rows (the 1st happens to be Sunday) are needed, but the number of days per month is 30, 31, and the 1st may not be Sunday. I simply designed it according to the most situation, with a total of 6 rows. The date of the month is not filled with the date of the previous month or the next month, which is convenient for calculation, and the height of the panel will not change when switching months. The array of date lists requires dynamic calculation, and Vue provides the computed property, so the date list dateList is directly written as a computed property. My approach is to fix the date list to an array of length 42 and then populate the dates for this month, last month, and next month in turn.

computed: {
 dateList () {
  //Get the number of days in the month  let currentMonthLength = new Date(,  + 1, 0).getDate()
  //First stuff the date of the month into the dateList  let dateList = ({length: currentMonthLength}, (val, index) => {
   return {
    currentMonth: true,
    value: index + 1
   }
  })
  //The week of the 1st of the month is to determine how many days it needs to be inserted before the 1st  let startDay = new Date(, , 1).getDay()
  //Confirm how many days last month  let previousMongthLength = new Date(, , 0).getDate()
 }
 //Insert the last month date before No. 1 for(let i = 0, len = startDay; i < len; i++){
  dateList = [{previousMonth: true, value: previousMongthLength - i}].concat(dateList)
 }
 //Complete the remaining positions for(let i = 0, item = 1; i < 42; i++, item++){
  dateList[] = {nextMonth: true, value: i}
 }
 return dateList
}

This is used to initialize an array, pass in an Array Like, and convert it into an array. When splicing strings, we use arr[] and [{}].concat(arr) methods, because we learned that this performance is better on JsTips, and we will post related links at the end of the article.
In this way, the date list is built and rendered using v-for loop in template

<ul class="date-list">
 <li v-for="item in dateList"
  v-text="" 
  :class="{preMonth: , nextMonth: ,
   selected: date ===  && month === tmpMonth && , invalid: validateDate(item)}"
  @click="selectDate(item)">
 </li>
</ul>

You can play your own style and write whatever you like. It should be noted that the cycle date may appear in the date of the previous month or this month. I marked it by previuosMonth, currentMonth and nextMonth respectively to provide judgment conditions for other functions.
The list of year and month is similar. I wrote the initial value of the year list directly in data, with the current year as the first. In order to be consistent with the month, 12 are displayed each time, and they are rendered through v-for.

data () {
 return {
  yearList: ({length: 12}, (value, index) => new Date().getFullYear() + index)
 }
}

Select date function

The selection order is: year -> month -> day, so we can control the content displayed in the panel through a state variable, and bind a suitable function to switch the display state.

<div>
 <div class="type-year" v-show="panelType === 'year'">
  <ul class="year-list">
   <li v-for="item in yearList"
    v-text="item"
    :class="{selected: item === tmpYear, invalid: validateYear(item)}" 
    @click="selectYear(item)"
   >
   </li>
  </ul>
 </div>
 <div class="type-month" v-show="panelType === 'month'">
  <ul class="month-list">
   <li v-for="item in monthList"
    v-text="item | month language"
    :class="{selected: $index === tmpMonth && year === tmpYear, invalid: validateMonth($index)}" 
    @click="selectMonth($index)"
   >
   </li>
  </ul>
 </div>
 <div class="type-date" v-show="panelType === 'date'">
  <ul class="date-list">
   <li v-for="item in dateList"
    v-text="" 
    track-by="$index" 
    :class="{preMonth: , nextMonth: ,
     selected: date ===  && month === tmpMonth && , invalid: validateDate(item)}"
    @click="selectDate(item)">
   </li>
  </ul>
 </div>
</div>

I won’t go into details about the method of selecting dates. In selectYear, selectMonth, assign values ​​to year and month variables, and then push panelType to the next step, the date selection function is realized.

However, before selecting a date, you may not want the real value of the current year and month to change, so in these methods, you can first assign the selected value to a temporary variable, and then all the values ​​will be assigned at once when selectDate.

selectMonth (month) {
 if((month)){
  return
 }else{
  //Temporary variable   = month
  //Switch panel status   = 'date'
 }
},
selectDate (date) {
 //validate logic above...
 //All values ​​are assigned at one time  = tmpYear
  = tmpMonth
  = 
  = `${}-${('0' + ( + 1)).slice(-2)}-${('0' + ).slice(-2)}`
 //After selecting the date, the panel will automatically hide  = false
}

Maximum/hour time limit

The maximum/small value needs to be passed from the parent component, so props should be used. In addition, this value can be a string or a variable (for example, there are two datepickers at the same time, and the second date cannot be larger than the first one). Therefore, the value should be passed in Dynamically bind.

&lt;datepicker :="start"&gt;&lt;/datepicker&gt;
&lt;!-- NowminThe value will followstartChanges from changes --&gt;
&lt;datepicker :="end" :min="start" &gt;&lt;/datepicker&gt;

Added restrictions. For illegal dates, the button should be grayed out. I used the method of comparing the timestamp to determine whether the date is legal, because even if the date in the current panel is New Year's Eve or Month, it will help you convert it to the corresponding legal value when created through the date constructor, saving you a lot of trouble in judgment:

new Date(2015, 0, 0).getTime() === new Date(2014, 11, 31).getTime() //true
new Date(2015, 12, 0).getTime() === new Date(2016, 0, 0).getTime() //true

Therefore, the function to verify whether the date is legal is like this:

validateDate (date) {
 let mon = 
 if(){
  mon -= 1
 }else if(){
  mon += 1
 }
 if(new Date(, mon, ).getTime() >= new Date(,  - 1, ).getTime()
  && new Date(, mon, ).getTime() <= new Date(,  - 1, ).getTime()){
  return false
 }
 return true
}

Dynamic calculation of position

When there is enough space to display on the right side of the page, the datepicker's panel will be positioned relative to the parent element left: 0. If there is not enough space, it should be placed in the right: 0. This can be achieved through the dynamic style and style objects provided by Vue (dynamic class and dynamic style are actually just special cases of dynamic props). When calculating the position, I put it in the ready period of the component declaration cycle, because the component has been inserted into the DOM tree at this time, and you can get the style for dynamic calculation:

ready () {
 if(this.$ + this.$ - this.$ &lt;= 300){
   = {right: '0', top: `${(this.$[0]).offsetHeight + 4}px`}
 }else{
   = {left: '0', top: `${(this.$[0]).offsetHeight + 4}px`}
 }
}
&lt;!-- templateThe corresponding dynamics instyle --&gt;
&lt;div :style="coordinates"&gt;&lt;/div&gt;

In order for the panel's obvious and implicit transition to smooth transitions, you can use transition to do transition animation. Here I simply use a 0.2-second transparency transition to make the obvious and implicit transition smoother.

<div :style="" v-show="panelState" transition="toggle"></div>

//less syntax
.toggle{
 &-transition{
  transition: all ease .2s;
 }
 &-enter, &-leave{
  opacity: 0;
 }
}

Switch between Chinese and English

It is actually very simple here. This multilingual switching is essentially a key outputs different values ​​according to different types, so using filter can easily implement it! For example, the rendering list of weeks:

&lt;ul class="weeks"&gt;
  &lt;li v-for="item in weekList" v-text="item | week language"&gt;&lt;/li&gt;
 &lt;/ul&gt;
 
filters : {
 week (item, lang){
  switch (lang) {
   case 'en':
    return {0: 'Su', 1: 'Mo', 2: 'Tu', 3: 'We', 4: 'Th', 5: 'Fr', 6: 'Sa'}[item]
   case 'ch':
    return {0: 'day', 1: 'one', 2: 'two', 3: 'three', 4: 'Four', 5: 'five', 6: 'six'}[item]
   default:
    return item
  }
 }
}

Multiple usage methods

For a Vue component, if you are writing a .vue single file using webpack + vue-loader, I want to use it like this:

//
<script>
 import datepicker from 'path/to/'
 export default {
  components: { datepicker}
 }
</script>

If it is used directly in the browser, then I hope that the datepicker component is exposed to the global level and can be used like this:

//
<html>
 <script src="path/to/"></script>
 <script src="path/to/"></script>
 <body>
  <div ></div>
  <script>
   new Vue({
    el: '#app',
    components: { datepicker }
   })
  </script>
 </body>
</html>

Here I chose webpack as a packaging tool. Using webpack and these two properties can package your bundle file as a library file. library defines the name of the library, libraryTarget defines the format you want to package. For details, you can see the documentation. I want my library to be loaded through datepicker and packaged into umd format, so mine is like this:

 = {
 entry: './',
 output: {
  path: './dist',
  library: 'datepicker',
  filename: '',
  libraryTarget: 'umd'
 },
 module: {
  loaders: [
   {test: /\.vue$/, loaders: ['vue']},
   {test: /\.js$/, exclude: /node_modules/, loaders: ['babel']}
  ]
 }
}

The packaged module is an umd format module, which can be used directly in the browser or with other module loaders!

Adapt to Vue

Vue 2.0 has been released for a while, and now we adapt the previous components to Vue 2.0. The migration process is still very smooth, the core API has not changed much, so you can use itvue-migration-helperFind the abandoned API and then gradually modify it. Here are only some APIs that I need to modify.

filter

The filter in 2.0 can only be used in mustache binding. If you want to bind filtered values ​​in directive binding, you can choose to calculate attributes. I used filters to filter language types in the month and week displays, but I used filters in directive bindings before, so I need to modify them as follows:

//Before modification&lt;div class="month-box" @click="chType('month')" v-text="tmpMonth + 1 | month language"&gt;&lt;/div&gt;
//After modifying, the filter parameter transfer method has also changed, becoming the style of function call&lt;div class="month-box" @click="chType('month')"&gt;{{tmpMonth + 1 | month(language)}}&lt;/div&gt;

Remove $index and $key

These two properties will not be automatically created in v-for. If you need to use it, you must declare it yourself in v-for:

<li v-for="item in monthList" @click="selectMonth($index)"></li>
//
<li v-for="(item, index) in monthList" @click="selectMonth(index)"></li>

ready life cycle removal

ready is removed from the life cycle hook, the migration method is simple, replaced by mounted and this.$nextTick.

Deprecated

Prop's sync is deprecated. The migration scheme is to use custom events, and the input-type component like Datepicker can be usedCustom events for form input componentsAs an alternative. Custom components can also use v-model directives, but two conditions must be met:

  1. Receive a value prop
  2. When the value changes, an input event is triggered and a new value is passed.

Therefore, the use of Datepicker is not <datepicker ="now"></datepicker>, but <datepicker v-model="now"></datepicker>. The way the component itself passes values ​​to the parent is different:

// Version, the value set to the value will be synchronized to the parent level = `${}-${('0' + ( + 1)).slice(-2)}-${('0' + ).slice(-2)}`

// Version, you need to trigger the input event by yourself and pass the new value back as a parameterlet value = `${}-${('0' + ( + 1)).slice(-2)}-${('0' + ).slice(-2)}`
this.$emit('input', value)

Summarize

The above is my general idea when writing this datepicker. It is also a very simple thing. I don’t go into it everywhere. I wrote it here as a summary of myself. If there are students who have just started using Vue, I hope this article can help you in your ideas: P. I would like to thank you for any advice for you guys: D, then that’s almost the same. I also hope everyone supports me.