网上已经有了各种各样的插件,满地的轮子,为什么我们还要自己封装呢?其实道理也很简单,因为不这些插件不是定制的,灵活性和可拓展性自然有所缺陷。就在前几天,产品和设计围过来说希望开发一个时间选择组件,看刚好上一次迭代已经结束了,就试着开发一下。
最开始打算用vue来封装的,但是项目采用的是amazeui和JQuery,无法直接使用vue,所以又想着封装一个JQuery插件。但是想想还是不合适,为什么不用原生javascript来做呢,这样不管什么框架都能兼容。
在开始前需要说明的是,本插件使用es6开发,通过babel
和uglifyjs
编译压缩成生产代码,会涉及到部分的nodejs
代码和javascript
原型相关的代码。
在线 demo
git项目:https://gitee.com/zhkumsg/date-selector
开发插件,创建个js文件就可以解决了,不过考虑到代码更新和推送到npmjs上,还是初始化一个完整的项目比较合适。
首先我们创建一个文件夹date-selector
,然后用npm初始化
$ mkdir date-selector
$ cd date-selector
$ npm init
紧接着我们新建几个文件,分别是index.html,src/index.js,lib/index.css,如下
+-- lib
| +-- index.css # 插件样式文件
+-- src
| +-- index.js # 插件原es6文件
+-- index.html # 首页
+-- package.json # 项目文件
其中lib
放css文件和打包后的js文件,src
放未经编译的es6代码,index.html
为html页面,用于实例化插件。
文件和目录已经准备好了,我们先在index.html上引入css和js代码,并加上相关内容。
我是一个输入框
然后我推荐使用一个npm模块,anywhere
来启动项目,它可以把任意目录当做一个web服务器来使用
# date-selector根目录
$ anywhere -v
启动后它会自动在浏览器中打开,当然也可使用其他方式浏览该页面。
当可以正常浏览页面后,我们先在index.html上把静态布局开发出来,开发不会涉及任何的逻辑,只是用css和标签构造出合适的效果。
由于只是普通的布局,真正布局会在js中动态渲染,所以这里先略过介绍。
了解布局方式后,我们开始index.js
的封装。本插件将通过实例化一个方法,自动在指定元素上显示插件内容,骨架代码如下
// src/index.js
(()=>{
function DateSelector(){
}
DateSelector.prototype = {
init(){ },
render(){ },
show(){ },
hide(){ },
}
// 导出供外部访问
window.DateSelector = DateSelector;
})()
为了避免污染全局变量,这里使用了匿名函数,只对外暴露DateSelector
方法。
为了更多的自定义配置,我们继续在DateSelector
方法上接受参数
function DateSelector({ target, data}){
this.target = target; // 目标dom
this.el = document.createElement('div'); // 新建dom
}
接受并保存参数后,我们要开始构造插件的内容了,也就是动态渲染html。
// 渲染日历头部代码
renderTop(date) {
return `
<<
<
${date.getFullYear()}年${date.getMonth() + 1}月
`;
},
// 渲染日历星期1-7代码
renderWeek() {
return `日一二三四五六`;
},
紧接着就是渲染日期,这也是比较复杂的部分,主要思路是动态算出当前页有多少行、当前月第一天起始位置,循环生成字符串。而且这里会设计到样式显示问题,所以还需要根据选中结果动态设置class
renderDays(date) {
let rows = [];
let start = this.getMonthStart(date);
let count = this.getMonthCount(date);
let year = date.getFullYear();
let month = date.getMonth() + 1;
const firstTime = this.selections[this.index][0]
? new Date(this.selections[this.index][0]).getTime()
: Number.MAX_VALUE;
const lastTime = this.selections[this.index][1]
? new Date(this.selections[this.index][1]).getTime()
: Number.MIN_VALUE;
for (let i = 0; i < Math.ceil((count + start) / 7); i++) {
rows.push('');
for (let j = 0; j < 7; j++) {
const day = i * 7 + j - start + 1;
let datadate = '';
let cls = ['day-item'];
const nonull = day > 0 && day <= count;
if (nonull) {
datadate = year + '-' + month + '-' + day;
const currentTime = new Date(datadate).getTime();
if (this.selections[this.index].includes(datadate)) {
cls.push('highlight');
} else if (currentTime > firstTime && currentTime < lastTime) {
cls.push('selected');
}
if (this._disabledDate instanceof Function)
if (this._disabledDate(new Date(datadate))) {
cls.push('notallow');
}
{
}
} else {
cls.push('null');
}
rows.push(`${nonull ? day : ''}`);
}
rows.push('');
}
return rows.join('');
},
到这里核心的渲染就已经完成了,后续的是如何把各个渲染串起来。
完成渲染后,我们再绑定事件,主要有上/下一年点击、上/下一月点击、日期点击、选中时间结果点击、添加时间区间点击、删除时间区间点击、遮罩点击、滚动监听。
比较有意思的是如何寻找目标元素位置和不添加遮罩层,通过坐位位置判断是否点击后隐藏插件功能的实现。
最后通过babel和uglify-js构建代码
$ babel ./src --presets babel-preset-es2015 --out-dir ./lib
$ uglifyjs lib/index.js -o lib/index.min.js
引入资源
引入 css 文件
引入 js 文件
实例化插件
var selector = new DateSelector({
target: document.querySelector('#box'),
data: [],
max: 5,
auto: true,
});
属性 | 类型 | 必填 | 作用 |
---|---|---|---|
target | dom | 必填 | 目标元素,将在这下面显示 |
data | Array | 选填 | 为二元数组,内层数组为选中结果 |
max | Number | 选填 | 默认为 1,表示最多选中一组 |
auto | Bool | 选填 | 默认为 false,代表自动显示 |
className | String | 选填 | 自定义类名 |
change | Function | 选填 | 修改后触发 |
confirm | Function | 选填 | 确认后触发 |
disabledDate | Function | 选填 | 设置禁用状态,参数为当前日期,要求返回 Boolean |
selector.show()
显示弹窗
selector.hide()
关闭弹窗
selector.toggle()
显示/隐藏弹窗
更多解析请看git项目:https://gitee.com/zhkumsg/date-selector