Home > Web Front-end > JS Tutorial > body text

How to write a datepicker using Vue

小云云
Release: 2018-01-29 15:21:33
Original
1935 people have browsed it

The most important role of jQuery is cross-browser. Although the browser market is not perfect now, it is far from as miserable as it used to be. The idea of ​​data-driven views is very popular. People are starting to use front-end frameworks to replace jQuery. I personally prefer I like Vue.js, so I want to try writing a component using Vue.js.

In order to publish it to npm, I changed the name of the project address, but the internal code has not been changed, and the usage is more convenient than before.

GitHub address: Here

Function & Expectation

This datepicker currently only implements some common functions:

  1. Select time (this is a bit redundant)

  2. Maximum/minimum time limit

  3. Chinese/English switch ( In fact, the week and month need to be switched)

  4. can be used in .vue form or directly in the browser environment

  5. No . . .

Directory structure

The first step in everything is still to create a project. It is just a single component and the structure is not complicated. Datepicker.vue is the most important component. file, dist is the output folder of webpack, index.js is the entry file for webpack packaging, and finally is the webpack configuration file, which is used to package our library files. So the project structure is like this:


.
├── Datepicker.vue
├── LICENSE
├── README.md
├── dist
│ └── vue-datepicker.js
├── index.js
├── package.json
└── webpack.config.js
Copy after login

Start with Datepicker.vue

Writing Vue components in .vue is a special way of writing. Each Vue file consists of three parts: template, script, and style. It is best not to use template as a fragment instance, so the outermost layer is first covered with a layer of p, which is used as the root element of the entire component. A datepicker generally consists of two parts, an input box used to display the date, and a panel used to select the date. Because I found that input will automatically evoke the keyboard on the mobile terminal, I did not use input and directly used p simulation, by clicking The event determines the visibility of the panel. Value is the final result and needs to communicate with the parent component, so the value is written as prop, and value.sync="xxx" is used in the parent component. The value of the datepicker is bidirectionally bound to the xxx of the parent component.


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

<scrip>
 export default {
  data () {
   return {
    panelState: false //初始值,默认panel关闭
   }
  },
  props: {
   value: String
  }
 }
</script>
Copy after login

Rendering date list

There are at least 28 days in a month. If Sunday is arranged at the beginning, then the least (the 1st is exactly Sunday) requires 4 lines, but the number of days in each month is 30 or 31, and the 1st is not necessarily Sunday, so I simply designed it according to the most common situation, with a total of 6 lines, and use it for the places that are not filled in the dates of the current month. The dates of the month or next month are filled in, which makes calculation easier, and the height of the panel will not change when switching months. The array of date lists needs to be dynamically calculated. Vue provides the computed attribute, so the date list dateList is directly written as a calculated attribute. My method is to fix the date list into an array with a length of 42, and then fill it with the dates of this month, last month, and next month in sequence.


computed: {
 dateList () {
  //获取当月的天数
  let currentMonthLength = new Date(this.tmpMonth, this.tmpMonth + 1, 0).getDate()
  //先将当月的日期塞入dateList
  let dateList = Array.from({length: currentMonthLength}, (val, index) => {
   return {
    currentMonth: true,
    value: index + 1
   }
  })
  //获取当月1号的星期是为了确定在1号前需要插多少天
  let startDay = new Date(this.year, this.tmpMonth, 1).getDay()
  //确认上个月一共多少天
  let previousMongthLength = new Date(this.year, this.tmpMonth, 0).getDate()
 }
 //在1号前插入上个月日期
 for(let i = 0, len = startDay; i < len; i++){
  dateList = [{previousMonth: true, value: previousMongthLength - i}].concat(dateList)
 }
 //补全剩余位置
 for(let i = 0, item = 1; i < 42; i++, item++){
  dateList[dateList.length] = {nextMonth: true, value: i}
 }
 return dateList
}
Copy after login

Here we use Array.from to initialize an array, pass in an Array Like, convert it into an array, and use arr[arr.length] when splicing strings. and [{}].concat(arr), because I learned from JsTips that this method has better performance, and relevant links will be posted at the end of the article.
In this way, the date list is constructed, and is rendered using a v-for loop in the template.


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

The style can be used by oneself, and you can write it however you like. . It should be noted that the cycle date may appear last month or this month. I marked it through previuosMonth, currentMonth and nextMonth respectively to provide judgment conditions for other functions.
The year and month lists are similar. I wrote the initial value of the year list directly in the data, with the current year as the first one. In order to be consistent with the month, 12 are displayed each time through v. -for rendering.


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

Select date function

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


<p>
 <p class="type-year" v-show="panelType === &#39;year&#39;">
  <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>
 </p>
 <p class="type-month" v-show="panelType === &#39;month&#39;">
  <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>
 </p>
 <p class="type-date" v-show="panelType === &#39;date&#39;">
  <ul class="date-list">
   <li v-for="item in dateList"
    v-text="item.value" 
    track-by="$index" 
    :class="{preMonth: item.previousMonth, nextMonth: item.nextMonth,
     selected: date === item.value && month === tmpMonth && item.currentMonth, invalid: validateDate(item)}"
    @click="selectDate(item)">
   </li>
  </ul>
 </p>
</p>
Copy after login

I won’t go into details about the method of selecting the date. Assign values ​​to the year and month variables in selectYear and selectMonth, and then push the panelType to the next step respectively to realize the date. Select function.

However, you may not want the actual value of the current year and month to change before selecting the date, so in these methods, you can first assign the selected value to a temporary variable, and wait until seletDate Then assign all values ​​at once.


selectMonth (month) {
 if(this.validateMonth(month)){
  return
 }else{
  //临时变量
  this.tmpMonth = month
  //切换panel状态
  this.panelType = &#39;date&#39;
 }
},
selectDate (date) {
 //validate logic above...
 //一次性全部赋值
 this.year = tmpYear
 this.month = tmpMonth
 this.date = date.value
 this.value = `${this.tmpYear}-${(&#39;0&#39; + (this.month + 1)).slice(-2)}-${(&#39;0&#39; + this.date).slice(-2)}`
 //选择完日期后,panel自动隐藏
 this.panelState = false
}
Copy after login

Maximum/minimum time limit

The maximum/minimum value needs to be passed down from the parent component, so props should be used. In addition, This value can be a string or a variable (for example, if there are two datepickers at the same time, the date of the second cannot be greater than the first), so the value should be passed using Dynamically bind.


<datepicker :value.sync="start"></datepicker>
<!-- 现在min的值会随着start的变化而变化 -->
<datepicker :value.sync="end" :min="start" ></datepicker>
Copy after login

Added restrictions. For illegal dates, the button should become grayed out. I used the method of comparing timestamps to determine whether the date is legal. Because even if the date in the current panel spans years or months, the date constructor will help you convert it into the corresponding legal value when you create it, 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
Copy after login

因此验证日期是否合法的函数是这样的:


validateDate (date) {
 let mon = this.tmpMonth
 if(date.previousMonth){
  mon -= 1
 }else if(date.nextMonth){
  mon += 1
 }
 if(new Date(this.tmpYear, mon, date.value).getTime() >= new Date(this.minYear, this.minMonth - 1, this.minDate).getTime()
  && new Date(this.tmpYear, mon, date.value).getTime() <= new Date(this.maxYear, this.maxMonth - 1, this.maxDate).getTime()){
  return false
 }
 return true
}
Copy after login

动态计算位置

当页面右侧有足够的空间显示时,datepicker的panel会定位为相对于父元素left: 0的位置,如果没有足够的空间,则应该置于right: 0的位置,这一点可以通过Vue提供的动态样式和样式对象来实现(动态class和动态style其实只是动态props的特例),而计算位置的时刻,我放在了组件声明周期的ready周期中,因为这时组件已经插入到DOM树中,可以获取style进行动态计算:


ready () {
 if(this.$el.parentNode.offsetWidth + this.$el.parentNode.offsetLeft - this.$el.offsetLeft <= 300){
  this.coordinates = {right: &#39;0&#39;, top: `${window.getComputedStyle(this.$el.children[0]).offsetHeight + 4}px`}
 }else{
  this.coordinates = {left: &#39;0&#39;, top: `${window.getComputedStyle(this.$el.children[0]).offsetHeight + 4}px`}
 }
}
<!-- template中对应的动态style -->
<p :style="coordinates"></p>
Copy after login

为了panel的显隐可以平滑过渡,可以使用transition做过渡动画,这里我简单地通过一个0.2秒的透明度过渡让显隐更平滑。


<p :style="this.coordinates" v-show="panelState" transition="toggle"></p>

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

中英文切换

这里其实也很简单,这种多语言切换实质就是一个key根据不同的type而输出不同的value,所以使用filter可以很容易的实现它!比如渲染星期的列表:


<ul class="weeks">
  <li v-for="item in weekList" v-text="item | week language"></li>
 </ul>
 
filters : {
 week (item, lang){
  switch (lang) {
   case &#39;en&#39;:
    return {0: &#39;Su&#39;, 1: &#39;Mo&#39;, 2: &#39;Tu&#39;, 3: &#39;We&#39;, 4: &#39;Th&#39;, 5: &#39;Fr&#39;, 6: &#39;Sa&#39;}[item]
   case &#39;ch&#39;:
    return {0: &#39;日&#39;, 1: &#39;一&#39;, 2: &#39;二&#39;, 3: &#39;三&#39;, 4: &#39;四&#39;, 5: &#39;五&#39;, 6: &#39;六&#39;}[item]
   default:
    return item
  }
 }
}
Copy after login

多种使用方式

对于一个Vue组件,如果是使用webpack + vue-loader的.vue单文件写法,我希望这样使用:


//App.vue
<script>
 import datepicker from &#39;path/to/datepicker.vue&#39;
 export default {
  components: { datepicker}
 }
</script>
Copy after login

如果是直接在浏览器中使用,那么我希望datepicker这个组件是暴露在全局下的,可以这么使用:


//index.html
<html>
 <script src="path/to/vue.js"></script>
 <script src="path/to/datepicker.js"></script>
 <body>
  <p id="app"></p>
  <script>
   new Vue({
    el: &#39;#app&#39;,
    components: { datepicker }
   })
  </script>
 </body>
</html>
Copy after login

这里我选择了webpack作为打包工具,使用webpack的output.library和output.linraryTarget这两个属性就可以把你的bundle文件作为库文件打包。library定义了库的名字,libraryTarget定义了你想要打包的格式,具体可以看文档。我希望自己的库可以通过datepicker加载到,并且打包成umd格式,因此我的webpack.config.js是这样的:


module.exports = {
 entry: &#39;./index.js&#39;,
 output: {
  path: &#39;./dist&#39;,
  library: &#39;datepicker&#39;,
  filename: &#39;vue-datepicker.js&#39;,
  libraryTarget: &#39;umd&#39;
 },
 module: {
  loaders: [
   {test: /\.vue$/, loaders: [&#39;vue&#39;]},
   {test: /\.js$/, exclude: /node_modules/, loaders: [&#39;babel&#39;]}
  ]
 }
}
Copy after login

打包完成的模块就是一个umd格式的模块啦,可以在浏览器中直接使用,也可以配合require.js等模块加载器使用!

适配 Vue 2.x

Vue 2.0已经发布有段时间了,现在把之前的组件适配到Vue 2.0。迁移过程还是很顺利的,核心API改动不大,可以借助vue-migration-helper来找出废弃的API再逐步修改。这里只列举一些我需要修改的API。

filter

2.0中的filter只能在mustache绑定中使用,如果想在指令式绑定中绑定过滤后的值,可以选择计算属性。我在月份和星期的显示中使用到了过滤器来过滤语言类型,但我之前是在指令式绑定中使用的filter,所以需要如下修改,:


//修改前
<p class="month-box" @click="chType(&#39;month&#39;)" v-text="tmpMonth + 1 | month language"></p>
//修改后,filter传参的方式也变了,变成了函数调用的风格
<p class="month-box" @click="chType(&#39;month&#39;)">{{tmpMonth + 1 | month(language)}}</p>
Copy after login

移除$index和$key

这两个属性不会在v-for中被自动创建了,如需使用,要在v-for中自行声明:


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

ready 生命周期移除

ready从生命周期钩子中移除了,迁移方法很简单,使用mounted和this.$nextTick来替换。

prop.sync弃用

prop的sync弃用了,迁移方案是使用自定义事件,而且Datepicker这种input类型组件,可以使用表单输入组件的自定义事件作为替换方案。自定义组件也可以使用v-model指令了,但是必须满足两个条件:

  1. 接收一个value的prop

  2. 值发生变化时,触发一个input事件,传入新值。

所以Datepicker的使用方式也不是了,而是。组件自身向父级传值的方式也不一样了:


//1.x版本,设置了value的值会同步到父级
this.value = `${this.tmpYear}-${(&#39;0&#39; + (this.month + 1)).slice(-2)}-${(&#39;0&#39; + this.date).slice(-2)}`

//2.x版本,需要自己触发input事件,将新值作为参数传递回去
let value = `${this.tmpYear}-${(&#39;0&#39; + (this.month + 1)).slice(-2)}-${(&#39;0&#39; + this.date).slice(-2)}`
this.$emit(&#39;input&#39;, value)
Copy after login

相关推荐:

Vue引用datepicker插件无法监听datepicker输入框的值怎么办

jQuery UI 日期选择器Datepicker详解

详解JS控件bootstrap datepicker的使用方法(图)

The above is the detailed content of How to write a datepicker using Vue. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template