JQuery日历插件
jQuery事件日历插件Calendar
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv=\'Content-Type\' content=\'text/html; charset=utf-8\'> <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=9; IE=8;"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href=\'http://fonts.googleapis.com/css?family=Roboto\' rel=\'stylesheet\' type=\'text/css\'> <title>jQuery事件日历插件Calendar</title> <link rel="stylesheet" href="css/calendar.css"> </head> <body> <div > <div ></div> <input type="text" placeholder="点击查看日历"> <div ></div> </div> <script src="js/jquery.js"></script> <script src="js/calendar.js"></script> <script> // $(\'#ca\').calendar({ // width: 320, // height: 320, // data: [ // { // date: \'2015/12/24\', // value: \'Christmas Eve\' // }, // { // date: \'2015/12/25\', // value: \'Merry Christmas\' // }, // { // date: \'2016/01/01\', // value: \'Happy New Year\' // } // ], // onSelected: function (view, date, data) { // console.log(\'view:\' + view) // alert(\'date:\' + date) // console.log(\'data:\' + (data || \'None\')); // } // }); $(\'#dd\').calendar({ trigger: \'#dt\', zIndex: 999, format: \'yyyy-mm-dd\', onSelected: function (view, date, data) { console.log(\'event: onSelected\') }, onClose: function (view, date, data) { console.log(\'event: onClose\') console.log(\'view:\' + view) console.log(\'date:\' + date) console.log(\'data:\' + (data || \'None\')); } }); </script> </body> </html>
html { font: 500 14px \'roboto\'; color: #333; background-color: #fafafa; } a { text-decoration: none; } ul, ol, li { list-style: none; padding: 0; margin: 0; } #demo { width: 300px; margin: 150px auto; } p { margin: 0; } #dt { margin: 30px auto; height: 28px; width: 200px; padding: 0 6px; border: 1px solid #ccc; outline: none; } .calendar { width: 280px; height: 330px; } .calendar-modal { display: none; position: absolute; background: #fdfdfd; border: 1px solid #e8e8e8; box-shadow: 1px 2px 3px #ddd } .calendar-inner { position: relative; z-index: 1; -webkit-perspective: 1000; -moz-perspective: 1000; -ms-perspective: 1000; perspective: 1000; -ms-transform: perspective(1000px); -moz-transform: perspective(1000px); -moz-transform-style: preserve-3d; -ms-transform-style: preserve-3d; } .calendar-views { transform-style: preserve-3d; } .calendar .view { backface-visibility: hidden; position: absolute; top: 0; left: 0; *overflow: hidden; -webkit-transition: .6s; transition: .6s; } .calendar-d .view-month, .calendar-m .view-date { transform: rotateY(180deg); visibility: hidden; z-index: 1; } .calendar-d .view-date, .calendar-m .view-month { transform: rotateY(0deg); visibility: visible; z-index: 2; } .calendar-ct, .calendar-hd, .calendar-views .week, .calendar-views .days { overflow: hidden; } .calendar-views { width: 100%; } .calendar .view, .calendar-display, .calendar-arrow .prev, .calendar .date-items li { float: left; } .calendar-arrow, .calendar-arrow .next { float: right; } .calendar-hd { padding: 10px 0; height: 30px; line-height: 30px; } .calendar-display { font-size: 28px; text-indent: 10px; } .view-month .calendar-hd { padding: 10px; } .calendar-arrow, .calendar-display { color: #ddd; } .calendar li[disabled] { color: #bbb; } .calendar li.old[disabled], .calendar li.new[disabled] { color: #eee; } .calendar-display .m, .calendar-views .week, .calendar-views .days .old, .calendar-views .days .new, .calendar-display:hover, .calendar-arrow span:hover { color: #888; } .calendar-arrow span, .calendar-views .days li[data-calendar-day], .calendar-views .view-month li[data-calendar-month] { cursor: pointer; } .calendar li[disabled] { cursor: not-allowed; } .calendar-arrow { width: 50px; margin-right: 10px; } .calendar-arrow span { font: 500 26px sans-serif; } .calendar ol li { position: relative; float: left; text-align: center; border-radius: 50%; } .calendar .week li, .calendar .days li { width: 40px; height: 40px; line-height: 40px; } .calendar .month-items li { width: 70px; height: 70px; line-height: 70px; } .calendar .days li[data-calendar-day]:hover, .calendar .view-month li[data-calendar-month]:hover { background: #eee; } .calendar .calendar-views .now { color: #fff; background: #FF808E!important; } .calendar .calendar-views .selected { color: #FF808E; background: #CDE9D9!important; } .calendar .calendar-views .dot { position: absolute; left: 50%; bottom: 4px; margin-left: -2px; width: 4px; height: 4px; background: #FF808E; border-radius: 50%; } .calendar-views .now .dot { background: #fff; } .calendar .date-items { width: 300%; margin-left: -100%; } .calendar-label { display: none; position: absolute; top: 50%; left: 50%; z-index: 2; padding: 5px 10px; line-height: 22px; color: #fff; background: #000; border-radius: 3px; opacity: .7; filter: alpha(opacity=70); } .calendar-label i { display: none; position: absolute; left: 50%; bottom: -12px; width: 0; height: 0; margin-left: -3px; border: 6px solid transparent; border-top-color: #000; }
(function(root, factory) { if (typeof define === \'function\' && define.amd) { define(\'calendar\', [\'jquery\'], factory); } else if (typeof exports === \'object\') { module.exports = factory(require(\'jquery\')); } else { factory(root.jQuery); } }(this, function($) { // default config var defaults = { // 宽度 width: 280, // 高度, 不包含头部,头部固定高度 height: 280, zIndex: 1, // selector or element // 设置触发显示的元素,为null时默认显示 trigger: null, // 偏移位置,可设正负值 // trigger 设置时生效 offset: [0, 1], // 自定义类,用于重写样式 customClass: \'\', // 显示视图 // 可选:date, month view: \'date\', // 默认日期为当前日期 date: new Date(), format: \'yyyy/mm/dd\', // 一周的第一天 // 0表示周日,依次类推 startWeek: 0, // 星期格式 weekArray: [\'日\', \'一\', \'二\', \'三\', \'四\', \'五\', \'六\'], // 设置选择范围 // 格式:[开始日期, 结束日期] // 开始日期为空,则无上限;结束日期为空,则无下限 // 如设置2015年11月23日以前不可选:[new Date(), null] or [\'2015/11/23\'] selectedRang: null, // 日期关联数据 [{ date: string, value: object }, ... ] // 日期格式与 format 一致 // 如 [ {date: \'2015/11/23\', value: \'面试\'} ] data: null, // 展示关联数据 // 格式化参数:{m}视图,{d}日期,{v}value // 设置 false 表示不显示 label: \'{d}\n{v}\', // 切换字符 prev: \'<\', next: \'>\', // 切换视图 // 参数:view, y, m viewChange: $.noop, // view: 视图 // date: 不同视图返回不同的值 // value: 日期关联数据 onSelected: function(view, date, value) { // body... }, // 参数同上 onMouseenter: $.noop, onClose: $.noop }, // static variable ACTION_NAMESPACE = \'data-calendar-\', DISPLAY_VD = \'[\' + ACTION_NAMESPACE + \'display-date]\', DISPLAY_VM = \'[\' + ACTION_NAMESPACE + \'display-month]\', ARROW_DATE = \'[\' + ACTION_NAMESPACE + \'arrow-date]\', ARROW_MONTH = \'[\' + ACTION_NAMESPACE + \'arrow-month]\', ITEM_DAY = ACTION_NAMESPACE + \'day\', ITEM_MONTH = ACTION_NAMESPACE + \'month\', DISABLED = \'disabled\', MARK_DATA = \'markData\', VIEW_CLASS = { date: \'calendar-d\', month: \'calendar-m\' }, OLD_DAY_CLASS = \'old\', NEW_DAY_CLASS = \'new\', TODAY_CLASS = \'now\', SELECT_CLASS = \'selected\', MARK_DAY_HTML = \'<i class="dot"></i>\', DATE_DIS_TPL = \'{year}/<span class="m">{month}</span>\', ITEM_STYLE = \'\', WEEK_ITEM_TPL = \'<li \' + ITEM_STYLE + \'>{wk}</li>\', DAY_ITEM_TPL = \'<li \' + ITEM_STYLE + \' class="{class}" {action}>{value}</li>\', MONTH_ITEM_TPL = \'<li \' + ITEM_STYLE + \' \' + ITEM_MONTH + \'>{m}月</li>\', TEMPLATE = [ \'<div class="calendar-inner">\', \'<div class="calendar-views">\', \'<div class="view view-date">\', \'<div class="calendar-hd">\', \'<a href="javascript:;" data-calendar-display-date class="calendar-display">\', \'{yyyy}/<span class="m">{mm}</span>\', \'</a>\', \'<div class="calendar-arrow">\', \'<span class="prev" title="上一月" data-calendar-arrow-date>{prev}</span>\', \'<span class="next" title="下一月" data-calendar-arrow-date>{next}</span>\', \'</div>\', \'</div>\', \'<div class="calendar-ct">\', \'<ol class="week">{week}</ol>\', \'<ul class="date-items"></ul>\', \'</div>\', \'</div>\', \'<div class="view view-month">\', \'<div class="calendar-hd">\', \'<a href="javascript:;" data-calendar-display-month class="calendar-display">{yyyy}</a>\', \'<div class="calendar-arrow">\', \'<span class="prev" title="上一年" data-calendar-arrow-month>{prev}</span>\', \'<span class="next" title="下一年" data-calendar-arrow-month>{next}</span>\', \'</div>\', \'</div>\', \'<ol class="calendar-ct month-items">{month}</ol>\', \'</div>\', \'</div>\', \'</div>\', \'<div class="calendar-label"><p>HelloWorld</p><i></i></div>\' ], OS = Object.prototype.toString; // utils function isDate(obj) { return OS.call(obj) === \'[object Date]\'; } function isString(obj) { return OS.call(obj) === \'[object String]\'; } function getClass(el) { return el.getAttribute(\'class\') || el.getAttribute(\'className\'); } // extension methods String.prototype.repeat = function(data) { return this.replace(/\{\w+\}/g, function(str) { var prop = str.replace(/\{|\}/g, \'\'); return data[prop] || \'\'; }); } String.prototype.toDate = function() { var dt = new Date(), dot = this.replace(/\d/g, \'\').charAt(0), arr = this.split(dot); dt.setFullYear(arr[0]); dt.setMonth(arr[1] - 1); dt.setDate(arr[2]); return dt; } Date.prototype.format = function(exp) { var y = this.getFullYear(), m = this.getMonth() + 1, d = this.getDate(); return exp.replace(\'yyyy\', y).replace(\'mm\', m).replace(\'dd\', d); } Date.prototype.isSame = function(y, m, d) { if (isDate(y)) { var dt = y; y = dt.getFullYear(); m = dt.getMonth() + 1; d = dt.getDate(); } return this.getFullYear() === y && this.getMonth() + 1 === m && this.getDate() === d; } Date.prototype.add = function(n) { this.setDate(this.getDate() + n); } Date.prototype.minus = function(n) { this.setDate(this.getDate() - n); } Date.prototype.clearTime = function(n) { this.setHours(0); this.setSeconds(0); this.setMinutes(0); this.setMilliseconds(0); return this; } Date.isLeap = function(y) { return (y % 100 !== 0 && y % 4 === 0) || (y % 400 === 0); } Date.getDaysNum = function(y, m) { var num = 31; switch (m) { case 2: num = this.isLeap(y) ? 29 : 28; break; case 4: case 6: case 9: case 11: num = 30; break; } return num; } Date.getSiblingsMonth = function(y, m, n) { var d = new Date(y, m - 1); d.setMonth(m - 1 + n); return { y: d.getFullYear(), m: d.getMonth() + 1 }; } Date.getPrevMonth = function(y, m, n) { return this.getSiblingsMonth(y, m, 0 - (n || 1)); } Date.getNextMonth = function(y, m, n) { return this.getSiblingsMonth(y, m, n || 1); } Date.tryParse = function(obj) { if (!obj) { return obj; } return isDate(obj) ? obj : obj.toDate(); } // Calendar class function Calendar(element, options) { this.$element = $(element); this.options = $.extend({}, $.fn.calendar.defaults, options); this.$element.addClass(\'calendar \' + this.options.customClass); this.width = this.options.width; this.height = this.options.height; this.date = this.options.date; this.selectedRang = this.options.selectedRang; this.data = this.options.data; this.init(); } Calendar.prototype = { constructor: Calendar, getDayAction: function(day) { var action = ITEM_DAY; if (this.selectedRang) { var start = Date.tryParse(this.selectedRang[0]), end = Date.tryParse(this.selectedRang[1]); if ((start && day < start.clearTime()) || (end && day > end.clearTime())) { action = DISABLED; } } return action; }, getDayData: function(day) { var ret, data = this.data; if (data) { for (var i = 0, len = data.length; i < len; i++) { var item = data[i]; if (day.isSame(item.date.toDate())) { ret = item.value; } } } return ret; }, getDayItem: function(y, m, d, f) { var dt = this.date, idt = new Date(y, m - 1, d), data = { w: this.width / 7, h: this.height / 7, value: d }, markData, $item; var selected = dt.isSame(y, m, d) ? SELECT_CLASS : \'\'; if (f === 1) { data[\'class\'] = OLD_DAY_CLASS; } else if (f === 3) { data[\'class\'] = NEW_DAY_CLASS; } else { data[\'class\'] = \'\'; } if (dt.isSame(y, m, d)) { data[\'class\'] += \' \' + TODAY_CLASS; } data.action = this.getDayAction(idt); markData = this.getDayData(idt); $item = $(DAY_ITEM_TPL.repeat(data)); if (markData) { $item.data(MARK_DATA, markData); $item.html(d + MARK_DAY_HTML); } return $item; }, getDaysHtml: function(y, m) { var year, month, firstWeek, daysNum, prevM, prevDiff, dt = this.date, $days = $(\'<ol class="days"></ol>\'); if (isDate(y)) { year = y.getFullYear(); month = y.getMonth() + 1; } else { year = Number(y); month = Number(m); } firstWeek = new Date(year, month - 1, 1).getDay() || 7; prevDiff = firstWeek - this.options.startWeek; daysNum = Date.getDaysNum(year, month); prevM = Date.getPrevMonth(year, month); prevDaysNum = Date.getDaysNum(year, prevM.m); nextM = Date.getNextMonth(year, month); // month flag var PREV_FLAG = 1, CURR_FLAG = 2, NEXT_FLAG = 3, count = 0; for (var p = prevDaysNum - prevDiff + 1; p <= prevDaysNum; p++, count++) { $days.append(this.getDayItem(prevM.y, prevM.m, p, PREV_FLAG)); } for (var c = 1; c <= daysNum; c++, count++) { $days.append(this.getDayItem(year, month, c, CURR_FLAG)); } for (var n = 1, nl = 42 - count; n <= nl; n++) { $days.append(this.getDayItem(nextM.y, nextM.m, n, NEXT_FLAG)); } return $(\'<li></li>\').width(this.options.width).append($days); }, getWeekHtml: function() { var week = [], weekArray = this.options.weekArray, start = this.options.startWeek, len = weekArray.length, w = this.width / 7, h = this.height / 7; for (var i = start; i < len; i++) { week.push(WEEK_ITEM_TPL.repeat({ w: w, h: h, wk: weekArray[i] })); } for (var j = 0; j < start; j++) { week.push(WEEK_ITEM_TPL.repeat({ w: w, h: h, wk: weekArray[j] })); } return week.join(\'\'); }, getMonthHtml: function() { var month = [], w = this.width / 4, h = this.height / 4, i = 1; for (; i < 13; i++) { month.push(MONTH_ITEM_TPL.repeat({ w: w, h: h, m: i })); } return month.join(\'\'); }, setMonthAction: function(y) { var m = this.date.getMonth() + 1; this.$monthItems.children().removeClass(TODAY_CLASS); if (y === this.date.getFullYear()) { this.$monthItems.children().eq(m - 1).addClass(TODAY_CLASS); } }, fillStatic: function() { var staticData = { prev: this.options.prev, next: this.options.next, week: this.getWeekHtml(), month: this.getMonthHtml() }; this.$element.html(TEMPLATE.join(\'\').repeat(staticData)); }, updateDisDate: function(y, m) { this.$disDate.html(DATE_DIS_TPL.repeat({ year: y, month: m })); }, updateDisMonth: function(y) { this.$disMonth.html(y); }, fillDateItems: function(y, m) { var ma = [ Date.getPrevMonth(y, m), { y: y, m: m }, Date.getNextMonth(y, m) ]; this.$dateItems.html(\'\'); for (var i = 0; i < 3; i++) { var $item = this.getDaysHtml(ma[i].y, ma[i].m); this.$dateItems.append($item); } }, hide: function(view, date, data) { this.$trigger.val(date.format(this.options.format)); this.options.onClose.call(this, view, date, data); this.$element.hide(); }, trigger: function() { this.$trigger = this.options.trigger instanceof $ ? this.options.trigger : $(this.options.trigger); var _this = this, $this = _this.$element, post = _this.$trigger.offset(), offs = _this.options.offset; $this.addClass(\'calendar-modal\').css({ left: (post.left + offs[0]) + \'px\', top: (post.top + _this.$trigger.outerHeight() + offs[1]) + \'px\', zIndex: _this.options.zIndex }); _this.$trigger.click(function() { $this.show(); }); $(document).click(function(e) { if (_this.$trigger[0] != e.target && !$.contains($this[0], e.target)) { $this.hide(); } }); }, render: function() { this.$week = this.$element.find(\'.week\'); this.$dateItems = this.$element.find(\'.date-items\'); this.$monthItems = this.$element.find(\'.month-items\'); this.$label = this.$element.find(\'.calendar-label\'); this.$disDate = this.$element.find(DISPLAY_VD); this.$disMonth = this.$element.find(DISPLAY_VM); var y = this.date.getFullYear(), m = this.date.getMonth() + 1; this.updateDisDate(y, m); this.updateMonthView(y); this.fillDateItems(y, m); this.options.trigger && this.trigger(); }, setView: function(view) { this.$element.removeClass(VIEW_CLASS.date + \' \' + VIEW_CLASS.month) .addClass(VIEW_CLASS[view]); this.view = view; }, updateDateView: function(y, m, dirc, cb) { m = m || this.date.getMonth() + 1; var _this = this, $dis = this.$dateItems, exec = { prev: function() { var pm = Date.getPrevMonth(y, m), ppm = Date.getPrevMonth(y, m, 2), $prevItem = _this.getDaysHtml(ppm.y, ppm.m); m = pm.m; y = pm.y; $dis.animate({ marginLeft: 0 }, 300, \'swing\', function() { $dis.children(\':last\').remove(); $dis.prepend($prevItem).css(\'margin-left\', \'-100%\'); $.isFunction(cb) && cb.call(_this); }); }, next: function() { var nm = Date.getNextMonth(y, m), nnm = Date.getNextMonth(y, m, 2), $nextItem = _this.getDaysHtml(nnm.y, nnm.m); m = nm.m; y = nm.y; $dis.animate({ marginLeft: \'-200%\' }, 300, \'swing\', function() { $dis.children(\':first\').remove(); $dis.append($nextItem).css(\'margin-left\', \'-100%\'); $.isFunction(cb) && cb.call(_this); }); } }; if (dirc) { exec[dirc](); } else { this.fillDateItems(y, m); } this.updateDisDate(y, m); this.setView(\'date\'); return { y: y, m: m }; }, updateMonthView: function(y) { this.updateDisMonth(y); this.setMonthAction(y); this.setView(\'month\'); }, getDisDateValue: function() { var arr = this.$disDate.html().split(\'/\'), y = Number(arr[0]), m = Number(arr[1].match(/\d{1,2}/)[0]); return [y, m]; }, selectedDay: function(d, type) { var arr = this.getDisDateValue(), y = arr[0], m = arr[1], toggleClass = function() { this.$dateItems.children(\':eq(1)\') .find(\'[\' + ITEM_DAY + \']:not(.\' + NEW_DAY_CLASS + \', .\' + OLD_DAY_CLASS + \')\') .removeClass(SELECT_CLASS) .filter(function(index) { return parseInt(this.innerHTML) === d; }).addClass(SELECT_CLASS); }; if (type) { var ret = this.updateDateView(y, m, { \'old\': \'prev\', \'new\': \'next\' }[type], toggleClass); y = ret.y; m = ret.m; this.options.viewChange(\'date\', y, m); } else { toggleClass.call(this); } return new Date(y, m - 1, d); }, showLabel: function(event, view, date, data) { var $lbl = this.$label; $lbl.find(\'p\').html(this.options.label.repeat({ m: view, d: date.format(this.options.format), v: data }).replace(/\n/g, \'<br>\')); var w = $lbl.outerWidth(), h = $lbl.outerHeight(); $lbl.css({ left: (event.pageX - w / 2) + \'px\', top: (event.pageY - h - 20) + \'px\' }).show(); }, hasLabel: function() { if (this.options.label) { $(\'body\').append(this.$label); return true; } return false; }, event: function() { var _this = this, vc = _this.options.viewChange; // view change _this.$element.on(\'click\', DISPLAY_VD, function() { var arr = _this.getDisDateValue(); _this.updateMonthView(arr[0], arr[1]); vc(\'month\', arr[0], arr[1]); }).on(\'click\', DISPLAY_VM, function() { var y = this.innerHTML; _this.updateDateView(y); vc(\'date\', y); }); // arrow _this.$element.on(\'click\', ARROW_DATE, function() { var arr = _this.getDisDateValue(), type = getClass(this), y = arr[0], m = arr[1]; var d = _this.updateDateView(y, m, type, function() { vc(\'date\', d.y, d.m); }); }).on(\'click\', ARROW_MONTH, function() { var y = Number(_this.$disMonth.html()), type = getClass(this); y = type === \'prev\' ? y - 1 : y + 1; _this.updateMonthView(y); vc(\'month\', y); }); // selected _this.$element.on(\'click\', \'[\' + ITEM_DAY + \']\', function() { var d = parseInt(this.innerHTML), cls = getClass(this), type = /new|old/.test(cls) ? cls.match(/new|old/)[0] : \'\'; var day = _this.selectedDay(d, type); _this.options.onSelected.call(this, \'date\', day, $(this).data(MARK_DATA)); _this.$trigger && _this.hide(\'date\', day, $(this).data(MARK_DATA)); }).on(\'click\', \'[\' + ITEM_MONTH + \']\', function() { var y = Number(_this.$disMonth.html()), m = parseInt(this.innerHTML); _this.updateDateView(y, m); vc(\'date\', y, m); _this.options.onSelected.call(this, \'month\', new Date(y, m - 1)); }); // hover _this.$element.on(\'mouseenter\', \'[\' + ITEM_DAY + \']\', function(e) { var arr = _this.getDisDateValue(), day = new Date(arr[0], arr[1] - 1, parseInt(this.innerHTML)); if (_this.hasLabel && $(this).data(MARK_DATA)) { $(\'body\').append(_this.$label); _this.showLabel(e, \'date\', day, $(this).data(MARK_DATA)); } _this.options.onMouseenter.call(this, \'date\', day, $(this).data(MARK_DATA)); }).on(\'mouseleave\', \'[\' + ITEM_DAY + \']\', function() { _this.$label.hide(); }); }, resize: function() { var w = this.width, h = this.height, hdH = this.$element.find(\'.calendar-hd\').outerHeight(); this.$element.width(w).height(h + hdH) .find(\'.calendar-inner, .view\') .css(\'width\', w + \'px\'); this.$element.find(\'.calendar-ct\').width(w).height(h); }, init: function() { this.fillStatic(); this.resize(); this.render(); this.view = this.options.view; this.setView(this.view); this.event(); }, setData: function(data) { this.data = data; if (this.view === \'date\') { var d = this.getDisDateValue(); this.fillDateItems(d[0], d[1]); } else if (this.view === \'month\') { this.updateMonthView(this.$disMonth.html()); } }, methods: function(name, args) { if (OS.call(this[name]) === \'[object Function]\') { return this[name].apply(this, args); } } }; $.fn.calendar = function(options) { var calendar = this.data(\'calendar\'), fn, args = [].slice.call(arguments); if (!calendar) { return this.each(function() { return $(this).data(\'calendar\', new Calendar(this, options)); }); } if (isString(options)) { fn = options; args.shift(); return calendar.methods(fn, args); } return this; } $.fn.calendar.defaults = defaults; }));
- 上一篇 »阅读项目:微信日历小程序插件
- 下一篇 »jQuery ui 中文日历