(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 = '', date_dis_tpl = '{year}/{month}', item_style = 'style="width:{w}px;height:{h}px;line-height:{h}px"', week_item_tpl = '
  • {wk}
  • ', day_item_tpl = '
  • {value}
  • ', month_item_tpl = '
  • {m}月
  • ', template = [ '
    ', '
    ', '
    ', '
    ', '', '{yyyy}/{mm}', '', '
    ', '{prev}', '{next}', '
    ', '
    ', '
    ', '
      {week}
    ', '
      ', '
      ', '
      ', '
      ', '
      ', '{yyyy}', '
      ', '{prev}', '{next}', '
      ', '
      ', '
        {month}
      ', '
      ', '
      ', '
      ', '

      helloworld

      ' ], 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 = $('
        '); 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 $('
      1. ').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, '
        ')); 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; }));