js原生自定义日期插件及遇到的问题
闲来无事,试着写了日期插件,不写不知道,一写吓一跳,需要注意的点还真多,废话不多说进入正题
对于日期插件,首先想到的是点击一个input,然后弹出一个选择框去选择时间,然后对里面的功能按钮进行操作,每一个按钮都是一个事件,想到的是面向对象编程,这样会复杂的业务逻辑简单化,增强代码复用性
上代码:
初始化定义一个日期函数
function DateContainer(elem, number, date, type) { var ele = elem.elem || elem; this.clickElem = document.querySelector(ele); this.eleClick(); };
对日期插件的div进行初始化
init: function(paramDate) { //初始化日期控件 var that = this; this.outerFrame = document.createElement('div'); //外部大框 this.outerFrame.id = 'outer_frame'; this.outerFrame.onselectstart = function() { return false }; //禁止选中变蓝 this.outerFrame.onclick = function() { that.stopBubble() }; this.dateShowOrDateBtn = document.createElement('div'); //上方日期按钮和显示 this.dateShowOrDateBtn.className = 'dateShow_or_dateBtn'; this.dateControlList = document.createElement('div'); //下方日期列表 this.dateControlList.className = 'dateControl_list'; this.dateControlListWeek = document.createElement('div'); //列表上部的星期 this.dateControlListWeek.className = 'dateControl_list_week' this.dateControlListDays = document.createElement('div'); //列表下部的日期 this.dateControlListDays.className = 'dateControl_list_days'; this.dateBtnLastYear = document.createElement('div'); //上一年按钮 this.dateBtnLastYear.className = 'dateBtn_lastYear'; this.dateBtnLastYear.innerHTML = '<span>《</span>'; this.dateBtnLastMonth = document.createElement('div'); //上一月按钮 this.dateBtnLastMonth.className = 'dateBtn_lastMonth'; this.dateBtnLastMonth.innerHTML = '<span><</span>'; this.dateShow = document.createElement('div'); //显示日期 this.dateShow.className = 'date_show'; this.dateBtnNextMonth = document.createElement('div'); //下一月按钮 this.dateBtnNextMonth.className = 'dateBtn_nextMonth'; this.dateBtnNextMonth.innerHTML = '<span>></span>'; this.dateBtnNextYear = document.createElement('div'); //下一年按钮 this.dateBtnNextYear.className = 'dateBtn_nextYear'; this.dateBtnNextYear.innerHTML = '<span>》</span>'; this.dateShowOrDateBtn.appendChild(this.dateBtnLastYear); this.dateShowOrDateBtn.appendChild(this.dateBtnLastMonth); this.dateShowOrDateBtn.appendChild(this.dateShow); this.dateShowOrDateBtn.appendChild(this.dateBtnNextMonth); this.dateShowOrDateBtn.appendChild(this.dateBtnNextYear); this.outerFrame.appendChild(this.dateShowOrDateBtn); this.outerFrame.appendChild(this.dateControlList); this.dateControlList.appendChild(this.dateControlListWeek); this.dateControlList.appendChild(this.dateControlListDays); this.clickElem.parentNode.appendChild(this.outerFrame); this.drawDateControlList(paramDate); this.drawShowOrBtn(paramDate); }
绘制插件上方的按钮和日期显示
drawShowOrBtn: function(paramDate) { //绘制上方日期显示和点击按钮 var m = paramDate || new Date(); var date = new Date(m); var year = date.getFullYear(); var month = date.getMonth() + 1; var date = date.getDate(); var that = this; this.dateBtnLastYear.onclick = function() { that.stopBubble(); that.delteListEle(); year--; that.drawDateControlList(year + ',' + month); }; this.dateBtnLastMonth.onclick = function() { that.stopBubble(); that.delteListEle(); month--; if (month === 0) { year--; month = 12; }; that.drawDateControlList(year + ',' + month); }; this.dateBtnNextMonth.onclick = function() { that.stopBubble(); that.delteListEle(); month++; if (month === 13) { year++; month = 1; }; that.drawDateControlList(year + ',' + month); }; this.dateBtnNextYear.onclick = function() { that.stopBubble(); that.delteListEle(); year++; that.drawDateControlList(year + ',' + month); }; }
绘制下方显示的日期列表
drawDateControlList: function(paramDate) { //绘制日期列表 var m = paramDate || new Date(); var date = new Date(m); var nowYear = date.getFullYear(); var nowMonth = date.getMonth() + 1; var nowDate = date.getDate(); var arrWeek = ['日', '一', '二', '三', '四', '五', '六']; var liWeek = ''; var firstDay = this.getFirstDayOfWeek(date); var lastDay = this.getLastDatOfWeek(date); var days = this.getDaysOfMonth(date); this.spanContainer = []; this.spanParent = this.dateControlListDays; for (var i = 0; i < arrWeek.length; i++) { liWeek += '<li>' + arrWeek[i] + '</li>'; } this.dateControlListWeek.innerHTML = `<ul>` + liWeek + `</ul>`; for (var i = 0; i < firstDay; i++) { //补全1号之前的空缺 var tempDate = new Date(date); tempDate.setDate(i - firstDay + 1); var span = document.createElement("span"); span.className = 'uglify'; this.spanContainer.push({ span: span, date: tempDate }); } date.setDate(1); //设置日期为1号,否则下面的第一个日期是当日日期 for (var i = 1; i <= days; i++) { //当月天数 var span = document.createElement("span"); span.className = 'beautify'; this.spanContainer.push({ span: span, date: new Date(date) }); date.setDate(i + 1); } for (var i = lastDay; i < 6; i++) { //补全最后一天后面的空缺 var span = document.createElement("span"); span.className = "uglify"; this.spanContainer.push({ span: span, date: new Date(date) }); date.setDate(date.getDate() + 1); } for (var i = 0; i < this.spanContainer.length; i++) { var spanList = this.spanContainer[i]; var span = spanList.span; span.year = spanList.date.getFullYear(); span.month = spanList.date.getMonth() + 1; span.date = spanList.date.getDate(); span.innerText = spanList.date.getDate(); if (span.month === nowMonth && span.date === nowDate) span.className += ' dateList_select'; //给默认当前日期加class var that = this; span.onclick = function() { that.stopBubble(); var target = event.target; var spanCssbg = target.parentElement.getElementsByClassName("dateList_select"); for (var i = 0; i < spanCssbg.length; i++) { spanCssbg[i].className = spanCssbg[i].className.replace(" dateList_select", ""); } target.className += " dateList_select"; that.inputChangeDate(target.year, target.month, target.date); that.showDate(target.year, target.month); that.clickElem.parentNode.removeChild(that.outerFrame); that.clickFlag = false; } this.showDate(nowYear, nowMonth); this.dateControlListDays.appendChild(span); } }
核心代码:
计算每一个月的天数
getDaysOfMonth: function(paramDate) { //计算一个月天数(传入参数最好是字符串类型 '2019,5') var m = paramDate || new Date(); var date = new Date(m); var month = date.getMonth(); var time = date.getTime(); var newTime = date.setMonth(month + 1); return Math.ceil((newTime - time) / (24 * 60 * 60 * 1000)); }
计算每个月1号是周几
getFirstDayOfWeek: function(paramDate) { //计算每个月1号是周几(传入参数最好是字符串类型 '2019,5',返回0是周日,其他一一对应,周一是1) var m = paramDate || new Date(); var date = new Date(m); date.setDate(1); var firstDay = date.getDay(); return firstDay; }
计算每个月最后一天是周几
getLastDatOfWeek: function(paramDate) { //计算每个月最后一天是周几(传入参数最好是字符串类型 '2019,5',返回0是周日,其他一一对应,周一是1) var m = paramDate || new Date(); var days = this.getDaysOfMonth(m); var date = new Date(m); date.setDate(days); var lastDay = date.getDay(); return lastDay; }
剩余代码:
inputChangeDate: function(year, month, date) { //填充到input中 this.clickElem.value = year + '-' + (month < 10 ? '0' + month : month) + '-' + (date < 10 ? '0' + date : date); }, delteListEle: function() { //删除节点 this.spanContainer = []; for (var i = this.spanParent.childNodes.length - 1; i >= 0; i--) { //i++方式删除不干净,因为删除的过程中length变小,影响了遍历 this.spanParent.removeChild(this.spanParent.childNodes[i]); } }, showDate: function(year, month) { //控件内部显示日期 var showDate = year + '年' + month + '月'; this.dateShow.innerHTML = '<span>' + showDate + '</span>' }, eleClick: function(paramDate) { //inuput点击事件 var that = this; this.clickFlag = false; this.clickElem.onmousedown = function() { that.stopBubble(); var inpVal = that.clickElem.value.replace(/[\-\,\/]/g, ','); if (that.clickFlag === false) { that.init(inpVal); that.clickFlag = true; return; } if (that.clickFlag === true) { return; }; }; this.blurFunc(); this.clickOtherAreaRemList(); }
遇到的问题:
1、在日期渲染上,要先计算每个月一号和最后一号分别得周几,然后要根据周几来计算前面和后面的几天用上个月和下个月来进行补全
2、在点中日期后,要关闭插件,但是想在打开插件之后点击其他地方,也要关闭插件,起初想在是利用onblur方法,在失去焦点的时候关闭插件,配合onmousedown事件,但是新的问题出现了,因为点击插件的时候,也会失去焦点,这样日期也没选上,所以想到用一个flag控制,在onblur中
在onmousedown中
这样是可以避免了日期选择不上的问题,但是在点击
按钮的时候,这时焦点已经消失了,方法已经走了,再次点击日期之后,插件不会关闭,
解决方法:
点击元素的时候阻止冒泡,
然后在全局点击的时候删除就好了
3、在input中输入数字,要计算年月日
这里面需要注意到,点击input的时候是空的还是非空,空的不处理,非空要重新渲染日期组件,在手动输入的时候要判断输入的是否有标点符号,提取出数字,然后计算年月日
最后补充效果图:
完整代码详见git: https://github.com/zzhewd/dateControl
如有帮助麻烦点一下star,就是对我最大的支持,欢迎小伙伴批评建议,也可以一起讨论
Email:544785380@qq.com
题外话:另外git上也有一些自己写的demo欢迎参阅,如有帮助麻烦点个star,如有转载请注明出处,谢谢
每一个努力的人都值得被肯定,但是最大的肯定来源于自己!!!
加油每一天!!!