手摸手系统:使用原生JS封装时间区间选择器插件

网上已经有了各种各样的插件,满地的轮子,为什么我们还要自己封装呢?其实道理也很简单,因为不这些插件不是定制的,灵活性和可拓展性自然有所缺陷。就在前几天,产品和设计围过来说希望开发一个时间选择组件,看刚好上一次迭代已经结束了,就试着开发一下。


手摸手系统:使用原生JS封装时间区间选择器插件_第1张图片
效果图

最开始打算用vue来封装的,但是项目采用的是amazeui和JQuery,无法直接使用vue,所以又想着封装一个JQuery插件。但是想想还是不合适,为什么不用原生javascript来做呢,这样不管什么框架都能兼容。

在开始前需要说明的是,本插件使用es6开发,通过babeluglifyjs编译压缩成生产代码,会涉及到部分的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和标签构造出合适的效果。

看效果图可以看出,该插件的ui有两大部分组成,左边是日历部分,右边是结果部分。
手摸手系统:使用原生JS封装时间区间选择器插件_第2张图片

由于只是普通的布局,真正布局会在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

你可能感兴趣的:(手摸手系统:使用原生JS封装时间区间选择器插件)