PageSourceSearch

https://www.4.works/assets/js/calendar/calendar.js

js 4.works collected 2026-09-24 13:54:07 UTC 25,031 bytes, 803 lines download raw bytes

1/*!
2 * @authors yusen
3 * @date    2017-01-04 21:34:19
4 * @github  https://github.com/yscoder/Calendar
5 */
6(function (root, factory) {
7    if (typeof define === 'function' && define.amd) {
8        define('calendar', ['jquery'], factory);
9    } else if (typeof exports === 'object') {
10        module.exports = factory(require('jquery'));
11    } else {
12        factory(root.jQuery);
13    }
14}(this, function ($) {
15
16    // default config
17
18    var defaults = {
19
20        // 宽度
21        width: 280,
22        // 高度, 不包含头部,头部固定高度
23        height: 280,
24
25        zIndex: 1,
26
27        // selector
28        // 设置触发显示的元素,为null时默认显示
29        trigger: null,
30
31        // 偏移位置,可设正负值
32        // trigger 设置时生效
33        offset: [0, 1],
34
35        // 自定义类,用于重写样式
36        customClass: '',
37
38        // 显示视图
39        // 可选:date, month
40        view: 'date',
41
42        // 默认日期为当前日期
43        date: new Date(),
44        format: 'yyyy/mm/dd',
45
46        // 一周的第一天
47        // 0表示周日,依次类推
48        startWeek: 0,
49
50        // 星期格式
51        weekArray: ['日', '一', '二', '三', '四', '五', '六'],
52
53        // 月份格式
54        monthArray: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
55
56        // 设置选择范围
57        // 格式:[开始日期, 结束日期]
58        // 开始日期为空,则无上限;结束日期为空,则无下限
59        // 如设置2015年11月23日以前不可选:[new Date(), null] or ['2015/11/23']
60        selectedRang: null,
61
62        // 日期关联数据 [{ date: string, value: object }, ... ]
63        // 日期格式与 format 一致
64        // 如 [ {date: '2015/11/23', value: '面试'} ]
65        data: null,
66
67        // 展示关联数据
68        // 格式化参数:{m}视图,{d}日期,{v}value
69        // 设置 false 表示不显示
70        label: '{d}\n{v}',
71
72        // 切换字符
73        prev: '<',
74        next: '>',
75
76        // 切换视图
77        // 参数:view, y, m
78        viewChange: $.noop,
79
80        // view: 视图
81        // date: 不同视图返回不同的值
82        // value: 日期关联数据
83        onSelected: function (view, date, value) {
84            // body...
85        },
86
87        // 参数同上
88        onMouseenter: $.noop,
89
90        onClose: $.noop
91    },
92
93        // static variable
94
95        ACTION_NAMESPACE = 'data-calendar-',
96
97        DISPLAY_VD = '[' + ACTION_NAMESPACE + 'display-date]',
98        DISPLAY_VM = '[' + ACTION_NAMESPACE + 'display-month]',
99
100        ARROW_DATE = '[' + ACTION_NAMESPACE + 'arrow-date]',
101        ARROW_MONTH = '[' + ACTION_NAMESPACE + 'arrow-month]',
102
103        ITEM_DAY = ACTION_NAMESPACE + 'day',
104        ITEM_MONTH = ACTION_NAMESPACE + 'month',
105
106        DISABLED = 'disabled',
107        MARK_DATA = 'markData',
108
109        VIEW_CLASS = {
110            date: 'calendar-d',
111            month: 'calendar-m'
112        },
113
114        OLD_DAY_CLASS = 'old',
115        NEW_DAY_CLASS = 'new',
116        TODAY_CLASS = 'now',
117        SELECT_CLASS = 'selected',
118        MARK_DAY_HTML = '<i class="dot"></i>',
119        DATE_DIS_TPL = '{year}/<span class="m">{month}</span>',
120
121        ITEM_STYLE = 'style="width:{w}px;height:{h}px;line-height:{h}px"',
122        WEEK_ITEM_TPL = '<li ' + ITEM_STYLE + '>{wk}</li>',
123        DAY_ITEM_TPL = '<li ' + ITEM_STYLE + ' class="{class}" {action}="{date}">{value}</li>',
124        MONTH_ITEM_TPL = '<li ' + ITEM_STYLE + ' ' + ITEM_MONTH + '>{m}</li>',
125
126        TEMPLATE = [
127            '<div class="calendar-inner">',
128            '<div class="calendar-views">',
129            '<div class="view view-date">',
130            '<div class="calendar-hd">',
131            '<a href="javascript:;" data-calendar-display-date class="calendar-display">',
132            '{yyyy}/<span class="m">{mm}</span>',
133            '</a>',
134            '<div class="calendar-arrow">',
135            '<span class="prev" data-calendar-arrow-date>{prev}</span>',
136            '<span class="next" data-calendar-arrow-date>{next}</span>',
137            '</div>',
138            '</div>',
139            '<div class="calendar-ct">',
140            '<ol class="week">{week}</ol>',
141            '<ul class="date-items"></ul>',
142            '</div>',
143            '</div>',
144            '<div class="view view-month">',
145            '<div class="calendar-hd">',
146            '<a href="javascript:;" data-calendar-display-month class="calendar-display">{yyyy}</a>',
147            '<div class="calendar-arrow">',
148            '<span class="prev" data-calendar-arrow-month>{prev}</span>',
149            '<span class="next" data-calendar-arrow-month>{next}</span>',
150            '</div>',
151            '</div>',
152            '<ol class="calendar-ct month-items">{month}</ol>',
153            '</div>',
154            '</div>',
155            '</div>',
156            '<div class="calendar-label"><p>HelloWorld</p><i></i></div>'
157        ],
158        OS = Object.prototype.toString;
159
160    // utils
161
162    function isDate(obj) {
163        return OS.call(obj) === '[object Date]';
164    }
165
166    function isString(obj) {
167        return OS.call(obj) === '[object String]';
168    }
169
170
171    function getClass(el) {
172        return el.getAttribute('class') || el.getAttribute('className');
173    }
174
175    // extension methods
176
177    String.prototype.repeat = function (data) {
178        return this.replace(/\{\w+\}/g, function (str) {
179            var prop = str.replace(/\{|\}/g, '');
180            return data[prop] || '';
181        });
182    }
183
184    String.prototype.toDate = function () {
185        var dt = new Date(),
186            dot = this.replace(/\d/g, '').charAt(0),
187            arr = this.split(dot);
188
189        return new Date(parseInt(arr[0]), parseInt(arr[1]) - 1, parseInt(arr[2]));
190    }
191
192    Date.prototype.format = function (exp) {
193        var y = this.getFullYear(),
194            m = this.getMonth() + 1,
195            d = this.getDate();
196
197        return exp.replace('yyyy', y).replace('mm', m).replace('dd', d);
198    }
199
200    Date.prototype.isSame = function (y, m, d) {
201        if (isDate(y)) {
202            var dt = y;
203            y = dt.getFullYear();
204            m = dt.getMonth() + 1;
205            d = dt.getDate();
206        }
207        return this.getFullYear() === y && this.getMonth() + 1 === m && this.getDate() === d;
208    }
209
210    Date.prototype.add = function (n) {
211        this.setDate(this.getDate() + n);
212    }
213
214    Date.prototype.minus = function (n) {
215        this.setDate(this.getDate() - n);
216    }
217
218    Date.prototype.clearTime = function (n) {
219        this.setHours(0);
220        this.setSeconds(0);
221        this.setMinutes(0);
222        this.setMilliseconds(0);
223        return this;
224    }
225
226    Date.isLeap = function (y) {
227        return (y % 100 !== 0 && y % 4 === 0) || (y % 400 === 0);
228    }
229
230    Date.getDaysNum = function (y, m) {
231        var num = 31;
232
233        switch (m) {
234            case 2:
235                num = this.isLeap(y) ? 29 : 28;
236                break;
237            case 4:
238            case 6:
239            case 9:
240            case 11:
241                num = 30;
242                break;
243        }
244        return num;
245    }
246
247    Date.getSiblingsMonth = function (y, m, n) {
248        var d = new Date(y, m - 1);
249        d.setMonth(m - 1 + n);
250        return {
251            y: d.getFullYear(),
252            m: d.getMonth() + 1
253        };
254    }
255
256    Date.getPrevMonth = function (y, m, n) {
257        return this.getSiblingsMonth(y, m, 0 - (n || 1));
258    }
259
260    Date.getNextMonth = function (y, m, n) {
261        return this.getSiblingsMonth(y, m, n || 1);
262    }
263
264    Date.tryParse = function (obj) {
265        if (!obj) {
266            return obj;
267        }
268        return isDate(obj) ? obj : obj.toDate();
269    }
270
271
272    // Calendar class
273
274    function Calendar(element, options) {
275        this.$element = $(element);
276        this.options = $.extend({}, $.fn.calendar.defaults, options);
277        this.$element.addClass('calendar ' + this.options.customClass);
278        this.width = this.options.width;
279        this.height = this.options.height;
280        this.date = this.options.date;
281        this.selectedRang = this.options.selectedRang;
282        this.data = this.options.data;
283        this.init();
284    }
285
286    Calendar.prototype = {
287        constructor: Calendar,
288        getDayAction: function (day) {
289            var action = ITEM_DAY;
290            if (this.selectedRang) {
291                var start = Date.tryParse(this.selectedRang[0]),
292                    end = Date.tryParse(this.selectedRang[1]);
293
294                if ((start && day < start.clearTime()) || (end && day > end.clearTime())) {
295                    action = DISABLED;
296                }
297            }
298
299            return action;
300        },
301        getDayData: function (day) {
302            var ret, data = this.data;
303
304            if (data) {
305
306                for (var i = 0, len = data.length; i < len; i++) {
307                    var item = data[i];
308
309                    if (day.isSame(Date.tryParse(item.date))) {
310                        ret = item.value;
311                    }
312                }
313            }
314
315            return ret;
316        },
317        getDayItem: function (y, m, d, f) {
318            var dt = this.date,
319                idt = new Date(y, m - 1, d),
320                data = {
321                    w: this.width / 7,
322                    h: this.height / 7,
323                    value: d
324                },
325                markData,
326                $item;
327
328            var selected = dt.isSame(y, m, d) ? SELECT_CLASS : '';
329            if (f === 1) {
330                data['class'] = OLD_DAY_CLASS;
331            } else if (f === 3) {
332                data['class'] = NEW_DAY_CLASS;
333            } else {
334                data['class'] = '';
335            }
336
337            if (dt.isSame(y, m, d)) {
338                data['class'] += ' ' + TODAY_CLASS;
339            }
340
341            data.date = idt.format(this.options.format);
342            data.action = this.getDayAction(idt);
343            markData = this.getDayData(idt);
344
345            $item = $(DAY_ITEM_TPL.repeat(data));
346
347            if (markData) {
348                $item.data(MARK_DATA, markData);
349                $item.html(d + MARK_DAY_HTML);
350            }
351
352            return $item;
353        },
354        getDaysHtml: function (y, m) {
355            var year, month, firstWeek, daysNum, prevM, prevDiff,
356                dt = this.date,
357                $days = $('<ol class="days"></ol>');
358
359            if (isDate(y)) {
360                year = y.getFullYear();
361                month = y.getMonth() + 1;
362            } else {
363                year = Number(y);
364                month = Number(m);
365            }
366
367            firstWeek = new Date(year, month - 1, 1).getDay() || 7;
368            prevDiff = firstWeek - this.options.startWeek;
369            daysNum = Date.getDaysNum(year, month);
370            prevM = Date.getPrevMonth(year, month);
371            prevDaysNum = Date.getDaysNum(year, prevM.m);
372            nextM = Date.getNextMonth(year, month);
373            // month flag
374            var PREV_FLAG = 1,
375                CURR_FLAG = 2,
376                NEXT_FLAG = 3,
377                count = 0;
378
379            for (var p = prevDaysNum - prevDiff + 1; p <= prevDaysNum; p++ , count++) {
380
381                $days.append(this.getDayItem(prevM.y, prevM.m, p, PREV_FLAG));
382            }
383
384            for (var c = 1; c <= daysNum; c++ , count++) {
385                $days.append(this.getDayItem(year, month, c, CURR_FLAG));
386            }
387
388            for (var n = 1, nl = 42 - count; n <= nl; n++) {
389
390                $days.append(this.getDayItem(nextM.y, nextM.m, n, NEXT_FLAG));
391            }
392
393            return $('<li></li>').width(this.options.width).append($days);
394        },
395        getWeekHtml: function () {
396            var week = [],
397                weekArray = this.options.weekArray,
398                start = this.options.startWeek,
399                len = weekArray.length,
400                w = this.width / 7,
401                h = this.height / 7;
402
403            for (var i = start; i < len; i++) {
404                week.push(WEEK_ITEM_TPL.repeat({
405                    w: w,
406                    h: h,
407                    wk: weekArray[i]
408                }));
409            }
410
411            for (var j = 0; j < start; j++) {
412                week.push(WEEK_ITEM_TPL.repeat({
413                    w: w,
414                    h: h,
415                    wk: weekArray[j]
416                }));
417            }
418
419            return week.join('');
420        },
421        getMonthHtml: function () {
422            var monthArray = this.options.monthArray,
423                month = [],
424                w = this.width / 4,
425                h = this.height / 4,
426                i = 0;
427
428            for (; i < 12; i++) {
429                month.push(MONTH_ITEM_TPL.repeat({
430                    w: w,
431                    h: h,
432                    m: monthArray[i]
433                }));
434            }
435
436            return month.join('');
437        },
438        setMonthAction: function (y) {
439            var m = this.date.getMonth() + 1;
440
441            this.$monthItems.children().removeClass(TODAY_CLASS);
442            if (y === this.date.getFullYear()) {
443                this.$monthItems.children().eq(m - 1).addClass(TODAY_CLASS);
444            }
445        },
446        fillStatic: function () {
447            var staticData = {
448                prev: this.options.prev,
449                next: this.options.next,
450                week: this.getWeekHtml(),
451                month: this.getMonthHtml()
452            };
453
454            this.$element.html(TEMPLATE.join('').repeat(staticData));
455        },
456        updateDisDate: function (y, m) {
457            this.$disDate.html(DATE_DIS_TPL.repeat({
458                year: y,
459                month: m
460            }));
461        },
462        updateDisMonth: function (y) {
463            this.$disMonth.html(y);
464        },
465        fillDateItems: function (y, m) {
466            var ma = [
467                Date.getPrevMonth(y, m), {
468                    y: y,
469                    m: m
470                },
471                Date.getNextMonth(y, m)
472            ];
473
474            this.$dateItems.html('');
475            for (var i = 0; i < 3; i++) {
476                var $item = this.getDaysHtml(ma[i].y, ma[i].m);
477                this.$dateItems.append($item);
478            }
479
480        },
481        hide: function (view, date, data) {
482            this.$trigger.val(date.format(this.options.format));
483            this.options.onClose.call(this, view, date, data);
484            this.$element.hide();
485        },
486        setPosition: function () {
487            var post = this.$trigger.offset();
488            var offs = this.options.offset;
489
490            this.$element.css({
491                left: (post.left + offs[0]) + 'px',
492                top: (post.top + this.$trigger.outerHeight() + offs[1]) + 'px'
493            })
494        },
495        trigger: function () {
496
497            this.$trigger = $(this.options.trigger);
498
499            var _this = this,
500                $this = _this.$element;
501
502            $this.addClass('calendar-modal').css('zIndex', _this.options.zIndex);
503
504            $(document).click(function (e) {
505                if (_this.$trigger[0] != e.target && !$.contains($this[0], e.target)) {
506                    $this.hide();
507                }
508            }).on('click', this.options.trigger, function () {
509                this.$trigger = $(this);
510                _this.setPosition();
511                $this.show();
512            })
513
514            $(window).resize(function () {
515                _this.setPosition();
516            });
517        },
518        render: function () {
519            this.$week = this.$element.find('.week');
520            this.$dateItems = this.$element.find('.date-items');
521            this.$monthItems = this.$element.find('.month-items');
522            this.$label = this.$element.find('.calendar-label');
523            this.$disDate = this.$element.find(DISPLAY_VD);
524            this.$disMonth = this.$element.find(DISPLAY_VM);
525
526            var y = this.date.getFullYear(),
527                m = this.date.getMonth() + 1;
528
529            this.updateDisDate(y, m);
530            this.updateMonthView(y);
531
532            this.fillDateItems(y, m);
533
534            this.options.trigger && this.trigger();
535
536        },
537        setView: function (view) {
538            this.$element.removeClass(VIEW_CLASS.date + ' ' + VIEW_CLASS.month)
539                .addClass(VIEW_CLASS[view]);
540            this.view = view;
541        },
542        updateDateView: function (y, m, dirc, cb) {
543            m = m || this.date.getMonth() + 1;
544
545            var _this = this,
546                $dis = this.$dateItems,
547                exec = {
548                    prev: function () {
549                        var pm = Date.getPrevMonth(y, m),
550                            ppm = Date.getPrevMonth(y, m, 2),
551                            $prevItem = _this.getDaysHtml(ppm.y, ppm.m);
552
553                        m = pm.m;
554                        y = pm.y;
555
556                        $dis.animate({
557                            marginLeft: 0
558                        }, 300, 'swing', function () {
559                            $dis.children(':last').remove();
560                            $dis.prepend($prevItem).css('margin-left', '-100%');
561
562                            $.isFunction(cb) && cb.call(_this);
563                        });
564                    },
565                    next: function () {
566                        var nm = Date.getNextMonth(y, m),
567                            nnm = Date.getNextMonth(y, m, 2),
568                            $nextItem = _this.getDaysHtml(nnm.y, nnm.m);
569
570                        m = nm.m;
571                        y = nm.y;
572
573                        $dis.animate({
574                            marginLeft: '-200%'
575                        }, 300, 'swing', function () {
576                            $dis.children(':first').remove();
577                            $dis.append($nextItem).css('margin-left', '-100%');
578
579                            $.isFunction(cb) && cb.call(_this);
580                        });
581
582                    }
583                };
584
585
586            if (dirc) {
587                exec[dirc]();
588            } else {
589                this.fillDateItems(y, m);
590            }
591
592            this.updateDisDate(y, m);
593
594            this.setView('date');
595
596            return {
597                y: y,
598                m: m
599            };
600        },
601        updateMonthView: function (y) {
602            this.updateDisMonth(y);
603            this.setMonthAction(y);
604            this.setView('month');
605        },
606        getDisDateValue: function () {
607            var arr = this.$disDate.html().split('/'),
608                y = Number(arr[0]),
609                m = Number(arr[1].match(/\d{1,2}/)[0]);
610
611            return [y, m];
612        },
613        selectedDay: function (d, type) {
614            var arr = this.getDisDateValue(),
615                y = arr[0],
616                m = arr[1],
617                toggleClass = function () {
618                    this.$dateItems.children(':eq(1)')
619                        .find('[' + ITEM_DAY + ']:not(.' + NEW_DAY_CLASS + ', .' + OLD_DAY_CLASS + ')')
620                        .removeClass(SELECT_CLASS)
621                        .filter(function (index) {
622                            return parseInt(this.innerHTML) === d;
623                        }).addClass(SELECT_CLASS);
624                };
625
626            if (type) {
627                var ret = this.updateDateView(y, m, {
628                    'old': 'prev',
629                    'new': 'next'
630                }[type], toggleClass);
631                y = ret.y;
632                m = ret.m;
633                this.options.viewChange('date', y, m);
634            } else {
635                toggleClass.call(this);
636            }
637
638            return new Date(y, m - 1, d);
639        },
640        showLabel: function (event, view, date, data) {
641            var $lbl = this.$label;
642
643            $lbl.find('p').html(this.options.label.repeat({
644                m: view,
645                d: date.format(this.options.format),
646                v: data
647            }).replace(/\n/g, '<br>'));
648
649            var w = $lbl.outerWidth(),
650                h = $lbl.outerHeight();
651
652            $lbl.css({
653                left: (event.pageX - w / 2) + 'px',
654                top: (event.pageY - h - 20) + 'px',
655                zIndex: this.options.zIndex + 1
656            }).show();
657        },
658        hasLabel: function () {
659            if (this.options.label) {
660                $('body').append(this.$label);
661                return true;
662            }
663            return false;
664        },
665        event: function () {
666            var _this = this,
667                vc = _this.options.viewChange;
668
669            // view change
670            _this.$element.on('click', DISPLAY_VD, function () {
671                var arr = _this.getDisDateValue();
672                _this.updateMonthView(arr[0], arr[1]);
673
674                vc('month', arr[0], arr[1]);
675
676            }).on('click', DISPLAY_VM, function () {
677                var y = this.innerHTML;
678
679                _this.updateDateView(y);
680                vc('date', y);
681            });
682
683            // arrow
684            _this.$element.on('click', ARROW_DATE, function () {
685                var arr = _this.getDisDateValue(),
686                    type = getClass(this),
687                    y = arr[0],
688                    m = arr[1];
689
690                var d = _this.updateDateView(y, m, type, function () {
691                    vc('date', d.y, d.m);
692                });
693
694            }).on('click', ARROW_MONTH, function () {
695
696                var y = Number(_this.$disMonth.html()),
697                    type = getClass(this);
698
699                y = type === 'prev' ? y - 1 : y + 1;
700                _this.updateMonthView(y);
701                vc('month', y);
702            });
703
704            // selected
705            _this.$element.on('click', '[' + ITEM_DAY + ']', function () {
706                var d = parseInt(this.innerHTML),
707                    cls = getClass(this),
708                    type = /new|old/.test(cls) ? cls.match(/new|old/)[0] : '';
709
710                var day = _this.selectedDay(d, type);
711
712                _this.options.onSelected.call(this, 'date', day, $(this).data(MARK_DATA));
713
714                _this.$trigger && _this.hide('date', day, $(this).data(MARK_DATA));
715
716            }).on('click', '[' + ITEM_MONTH + ']', function () {
717                var y = Number(_this.$disMonth.html()),
718                    m = $(this).index() + 1;
719
720                _this.updateDateView(y, m);
721                vc('date', y, m);
722                _this.options.onSelected.call(this, 'month', new Date(y, m - 1));
723            });
724
725            // hover
726            _this.$element.on('mouseenter', '[' + ITEM_DAY + ']', function (e) {
727                var $this = $(this),
728                    day = $this.attr(ITEM_DAY).toDate();
729
730                if (_this.hasLabel() && $this.data(MARK_DATA)) {
731                    _this.showLabel(e, 'date', day, $this.data(MARK_DATA));
732                }
733
734                _this.options.onMouseenter.call(this, 'date', day, $this.data(MARK_DATA));
735            }).on('mouseleave', '[' + ITEM_DAY + ']', function () {
736                _this.$label.hide();
737            });
738        },
739        resize: function () {
740            var w = this.width,
741                h = this.height,
742                hdH = this.$element.find('.calendar-hd').outerHeight();
743
744            this.$element.width(w).height(h + hdH)
745                .find('.calendar-inner, .view')
746                .css('width', w + 'px');
747
748            this.$element.find('.calendar-ct').width(w).height(h);
749
750        },
751        init: function () {
752
753            this.fillStatic();
754            this.resize();
755            this.render();
756            this.view = this.options.view;
757            this.setView(this.view);
758            this.event();
759        },
760        setData: function (data) {
761            this.data = data;
762
763            if (this.view === 'date') {
764                var d = this.getDisDateValue();
765                this.fillDateItems(d[0], d[1]);
766            } else if (this.view === 'month') {
767                this.updateMonthView(this.$disMonth.html());
768            }
769        },
770        setDate: function (date) {
771            var dateObj = Date.tryParse(date);
772            this.updateDateView(dateObj.getFullYear(), dateObj.getMonth() + 1);
773            this.selectedDay(dateObj.getDate());
774        },
775        methods: function (name, args) {
776            if (OS.call(this[name]) === '[object Function]') {
777                return this[name].apply(this, args);
778            }
779        }
780    };
781
782    $.fn.calendar = function (options) {
783        var calendar = this.data('calendar'),
784            fn,
785            args = [].slice.call(arguments);
786
787        if (!calendar) {
788            return this.each(function () {
789                return $(this).data('calendar', new Calendar(this, options));
790            });
791        }
792        if (isString(options)) {
793            fn = options;
794            args.shift();
795            return calendar.methods(fn, args);
796        }
797
798        return this;
799    }
800
801    $.fn.calendar.defaults = defaults;
802
803}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.