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

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


13908708-a9c50d83f225b4ed.png
效果图

最开始打算用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代码,并加上相关内容。

<link rel="stylesheet" href="./lib/index.css" />

<div id="box">我是一个输入框</div>

<script src="./src/index.js"></script>

然后我推荐使用一个npm模块,anywhere来启动项目,它可以把任意目录当做一个web服务器来使用

# date-selector根目录
$ anywhere -v

启动后它会自动在浏览器中打开,当然也可使用其他方式浏览该页面。


当可以正常浏览页面后,我们先在index.html上把静态布局开发出来,开发不会涉及任何的逻辑,只是用css和标签构造出合适的效果。

看效果图可以看出,该插件的ui有两大部分组成,左边是日历部分,右边是结果部分。
13908708-4772530c244f20e0.png

由于只是普通的布局,真正布局会在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 `
    <div class="top">
        <span class="goto-btn prev-year">&lt;&lt;</span>
        <span class="goto-btn prev-month">&lt;</span>
        <span class="goto-date">${date.getFullYear()}年${date.getMonth() + 1}月</span>
        <span class="goto-btn next-month">&gt;</span>
        <span class="goto-btn next-year">&gt;&gt;</span>
    </div>`;
},
// 渲染日历星期1-7代码
renderWeek() {
    return `<div class="week"><span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span></div>`;
},

紧接着就是渲染日期,这也是比较复杂的部分,主要思路是动态算出当前页有多少行、当前月第一天起始位置,循环生成字符串。而且这里会设计到样式显示问题,所以还需要根据选中结果动态设置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('<div class="day-row">');
        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(`<span data-date="${datadate}" class="${cls.join(' ')}">${nonull ? day : ''}</span>`);
        }
        rows.push('</div>');
    }
    return rows.join('');
},

到这里核心的渲染就已经完成了,后续的是如何把各个渲染串起来。

交互逻辑封装

完成渲染后,我们再绑定事件,主要有上/下一年点击、上/下一月点击、日期点击、选中时间结果点击、添加时间区间点击、删除时间区间点击、遮罩点击、滚动监听。

比较有意思的是如何寻找目标元素位置和不添加遮罩层,通过坐位位置判断是否点击后隐藏插件功能的实现。

插件构建使用

最后通过babel和uglify-js构建代码

$ babel ./src --presets babel-preset-es2015 --out-dir ./lib
$ uglifyjs lib/index.js -o lib/index.min.js

使用方式

引入资源

<input id="box" type="text" />

引入 css 文件

<link rel="stylesheet" href="http://zhkumsg.gitee.io/date-selector/lib/index.css" />

引入 js 文件

<script src="http://zhkumsg.gitee.io/date-selector/lib/index.min.js"></script>

实例化插件

var selector = new DateSelector({
    target: document.querySelector('#box'),
    data: [],
    max: 5,
    auto: true,
});

属性介绍

属性类型必填作用
targetdom必填目标元素,将在这下面显示
dataArray选填为二元数组,内层数组为选中结果
maxNumber选填默认为 1,表示最多选中一组
autoBool选填默认为 false,代表自动显示
classNameString选填自定义类名
changeFunction选填修改后触发
confirmFunction选填确认后触发
disabledDateFunction选填设置禁用状态,参数为当前日期,要求返回 Boolean

selector.show() 显示弹窗

selector.hide() 关闭弹窗

selector.toggle() 显示/隐藏弹窗

更多解析请看git项目:https://gitee.com/zhkumsg/date-selector

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值