媒介
写插件是很有意义,也很磨炼人,由于这个历程当中能发明许多的细节题目。在前端生长的历程当中,jQuery无疑是一个主要的里程碑,围绕着这个优异项目也涌现了许多优异的插件能够直接运用,大大节省了开发者们的时候。jQuery最主要的作用是跨浏览器,而如今浏览器市场虽不圆满,但已远没有夙昔那末惨,数据驱动视图的头脑倍受欢迎,人人开始运用前端框架庖代jQuery,我个人比较喜好Vue.js,所以想试着用Vue.js写一个组件出来。
为了宣布到npm上,所以给项目地点改名字了,然则内部代码没有改,运用要领比之前轻易。
Demo演示: Here
GitHub地点: Here
愿望人人能给个star
功用&希冀
这个datepicker现在仅完成了一些经常使用的功用:
挑选时候(这话说得有点过剩)
最大/最小时候限定
中/英文切换(实在也就礼拜和月份须要切换)
能够以
.vue
情势运用,也可在浏览器环境中直接运用没了。。。
目次组织
万事的第一步依然是建立项目,只是单一组件,组织并不庞杂,Datepicker.vue是最主要的组件文件,dist是webpack的输出文件夹,index.js是webpack打包的进口文件,末了是webpack的配置文件,用来对我们的库文件举行打包用的。因而项目组织就是如许:
.
├── Datepicker.vue
├── LICENSE
├── README.md
├── dist
│ └── vue-datepicker.js
├── index.js
├── package.json
└── webpack.config.js
从Datepicker.vue入手
以.vue
的体式格局写Vue组件是一种特别写法,每一个Vue文件包含template
, script
, style
三部份,template
最好不要成为片断实例,所以最外层先套一层div
,当作悉数组件的根元素。一个datepicker平常由两部份构成,一个用来显现日期的input框,一个用来挑选日期的panel,由于我发明input在挪动端会自动唤起键盘,所以没有运用input,直接用了div模仿,经由历程点击事宜决议panel的显隐。value
是终究的效果,须要和父组件通讯,所以将value写成了prop,在父组件中运用value.sync="xxx"
,datepicker的value就和父组件的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 //初始值,默许panel封闭
}
},
props: {
value: String
}
}
</script>
衬着日期列表
一个月起码是28天,假如把周日排在开首,那末起码(1号恰好是周日)须要4行,然则每月天数30,31占多数,而且1号又不一定是周日,我痛快痛快按最多的状况设想了,共6行,当月日期没填满的处所用上个月或下个月的日期补齐,如许就轻易盘算了,而且切换月份时候panel高度不会变化。日期列表的数组须要动态盘算,Vue供应了computed这个属性,所以直接将日期列表dateList
写成盘算属性。我的要领是将日期列表固定为长度为42的数组,然后将本月,上个月,下个月的日期顺次添补。
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
}
这里用Array.from
来初始化了一个数组,传入一个Array Like,转化成数组,在拼接字符串时候采用了arr[arr.length]
和[{}].concat(arr)
这类体式格局,由于在JsTips上学到如许做机能更好,文章的末了会贴出相干链接。
如许,日期列表就构建好了,在template中运用v-for
轮回衬着出来
<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>
款式上就能够自身发挥了,怎样喜好怎样写。须要注重的是轮回日期可能会涌现上个月或这个月的日期,我经由历程previuosMonth
,currentMonth
和nextMonth
离别做了标记,对其他功用供应推断前提。
年份和月份的列表都是差不多的原理,年份列表的初始值我直接写在了data
里,以当前年份为第一个,为了和月份保持一致,每次显现12个,都经由历程v-for
衬着。
data () {
return {
yearList: Array.from({length: 12}, (value, index) => new Date().getFullYear() + index)
}
}
挑选日期功用
挑选递次是:年 -> 月 -> 日,所以我们能够经由历程一个状况变量来掌握panel中显现的内容,绑定合适的函数切换显现状况。
<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="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>
</div>
</div>
挑选日期的要领就不细说了,在selectYear
,selectMonth
中对年份,月份变量赋值,再离别将panelType
推向下一步就完成了日期挑选功用。
不过在未挑选完日期之前,你可能不愿望当前年月的实在值发生变化,所以在这些要领中可先将挑选的值赋给一个暂时变量,比及seletDate
的时候再一次性悉数赋值。
selectMonth (month) {
if(this.validateMonth(month)){
return
}else{
//暂时变量
this.tmpMonth = month
//切换panel状况
this.panelType = 'date'
}
},
selectDate (date) {
//validate logic above...
//一次性悉数赋值
this.year = tmpYear
this.month = tmpMonth
this.date = date.value
this.value = `${this.tmpYear}-${('0' + (this.month + 1)).slice(-2)}-${('0' + this.date).slice(-2)}`
//挑选完日期后,panel自动隐藏
this.panelState = false
}
最大/小时候限定
最大/小值是须要从父组件通报下来的,因而应当运用props
,别的,这个值能够是字符串,也应当能够是变量(比方同时存在两个datepicker,第二个的日期不能比第一个大这类逻辑),所以应当运用Dynamically bind的体式格局传值。
<datepicker :value.sync="start"></datepicker>
<!-- 如今min的值会跟着start的变化而变化 -->
<datepicker :value.sync="end" :min="start" ></datepicker>
增加了限定前提,关于不正当的日期,其按钮应当变成置灰状况,我用了比较时候戳的体式格局来推断日期是不是正当,由于就算当前panel中的日期是跨年或是跨月的,经由历程日期组织函数建立时都邑帮你转换成对应的正当值,省去许多推断的贫苦:
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
因而考证日期是不是正当的函数是如许的:
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
}
动态盘算位置
当页面右边有充足的空间显现时,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: '0', top: `${window.getComputedStyle(this.$el.children[0]).offsetHeight + 4}px`}
}else{
this.coordinates = {left: '0', top: `${window.getComputedStyle(this.$el.children[0]).offsetHeight + 4}px`}
}
}
<!-- template中对应的动态style -->
<div :style="coordinates"></div>
为了panel的显隐能够腻滑过渡,能够运用transition
做过渡动画,这里我简朴地经由历程一个0.2秒的透明度过渡让显隐更腻滑。
<div :style="this.coordinates" v-show="panelState" transition="toggle"></div>
//less syntax
.toggle{
&-transition{
transition: all ease .2s;
}
&-enter, &-leave{
opacity: 0;
}
}
中英文切换
这里实在也很简朴,这类多言语切换本质就是一个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 'en':
return {0: 'Su', 1: 'Mo', 2: 'Tu', 3: 'We', 4: 'Th', 5: 'Fr', 6: 'Sa'}[item]
case 'ch':
return {0: '日', 1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六'}[item]
default:
return item
}
}
}
多种运用体式格局
关于一个Vue组件,假如是运用webpack + vue-loader
的.vue
单文件写法,我愿望如许运用:
//App.vue
<script>
import datepicker from 'path/to/datepicker.vue'
export default {
components: { datepicker}
}
</script>
假如是直接在浏览器中运用,那末我愿望datepicker
这个组件是暴露在全局下的,能够这么运用:
//index.html
<html>
<script src="path/to/vue.js"></script>
<script src="path/to/datepicker.js"></script>
<body>
<div id="app"></div>
<script>
new Vue({
el: '#app',
components: { datepicker }
})
</script>
</body>
</html>
这里我挑选了webpack作为打包东西,运用webpack的output.library
和output.linraryTarget
这两个属性就能够把你的bundle文件作为库文件打包。library
定义了库的名字,libraryTarget
定义了你想要打包的花样,详细能够看文档。我愿望自身的库能够经由历程datepicker
加载到,而且打包成umd
花样,因而我的webpack.config.js
是如许的:
module.exports = {
entry: './index.js',
output: {
path: './dist',
library: 'datepicker',
filename: 'vue-datepicker.js',
libraryTarget: 'umd'
},
module: {
loaders: [
{test: /\.vue$/, loaders: ['vue']},
{test: /\.js$/, exclude: /node_modules/, loaders: ['babel']}
]
}
}
打包完成的模块就是一个umd
花样的模块啦,能够在浏览器中直接运用,也能够合营require.js等模块加载器运用!
适配 Vue 2.x
Vue 2.0已宣布有段时候了,如今把之前的组件适配到Vue 2.0。迁徙历程照样很顺遂的,中心API修正不大,能够借助vue-migration-helper来找出烧毁的API再逐渐修正。这里只枚举一些我须要修正的API。
filter
2.0中的filter只能在mustache绑定中运用,假如想在指令式绑定中绑定过滤后的值,能够挑选盘算属性。我在月份和礼拜的显现中运用到了过滤器来过滤言语范例,但我之前是在指令式绑定中运用的filter,所以须要以下修正,:
//修正前
<div class="month-box" @click="chType('month')" v-text="tmpMonth + 1 | month language"></div>
//修正后,filter传参的体式格局也变了,变成了函数挪用的作风
<div class="month-box" @click="chType('month')">{{tmpMonth + 1 | month(language)}}</div>
移除$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>
ready
生命周期移除
ready
从生命周期钩子中移除了,迁徙要领很简朴,运用mounted
和this.$nextTick
来替代。
prop.sync
弃用
prop
的sync
弃用了,迁徙计划是运用自定义事宜,而且Datepicker这类input范例组件,能够运用表单输入组件的自定义事宜作为替代计划。自定义组件也能够运用v-model
指令了,然则必需满足两个前提:
吸收一个
value
的prop
值发生变化时,触发一个
input
事宜,传入新值。
所以Datepicker的运用体式格局也不是<datepicker value.sync="now"></datepicker>
了,而是<datepicker v-model="now"></datepicker>
。组件自身向父级传值的体式格局也不一样了:
//1.x版本,设置了value的值会同步到父级
this.value = `${this.tmpYear}-${('0' + (this.month + 1)).slice(-2)}-${('0' + this.date).slice(-2)}`
//2.x版本,须要自身触发input事宜,将新值作为参数通报归去
let value = `${this.tmpYear}-${('0' + (this.month + 1)).slice(-2)}-${('0' + this.date).slice(-2)}`
this.$emit('input', value)
总结
以上就是我在写这个datepicker时的大抵思绪,自身也是很简朴的事变,没有到处睁开来讲,写在这里作为自身的一个总结,假如有刚开始运用Vue的同砚也愿望这篇文章能够在思绪上协助到你们:P,关于列位老鸟假如有什么指导的处所我也很谢谢:D,那末差不多就如许,背面贴一些相干引荐浏览。
引荐浏览
高效地向数组中插值
Vue.js-片断实例
Vue.js-动态绑定
Js日期对象基本
Webpack: export bundle as library
UMD(universial Module Defination)
Migration from Vue 1.x