├── .gitignore
├── README.md
├── dist
├── calendar.css
└── calendar.js
├── gulpfile.js
├── index.html
├── package.json
└── src
├── calendar.css
└── calendar.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # build dir
7 | public
8 |
9 | # e2e test reports
10 | test/e2e/reports
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # nyc test coverage
24 | .nyc_output
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules
37 | jspm_packages
38 |
39 | # Optional npm cache directory
40 | .npm
41 |
42 | # Optional REPL history
43 | .node_repl_history
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Calendar
2 | A calendar component, based on jQuery. [Demo](//yscoder.github.io/Calendar/index.html)
3 |
4 | ---
5 |
6 | ## Depend
7 |
8 | ```
9 |
10 |
11 |
12 | ```
13 |
14 | ## Use
15 |
16 | ```html
17 |
18 |
19 |
24 | ```
25 |
26 | or
27 |
28 | ```html
29 |
30 |
31 |
37 | ```
38 |
39 | ## Options
40 |
41 | ```js
42 | {
43 |
44 | // width
45 | width: 280,
46 |
47 | // height,
48 | height: 280,
49 |
50 | // zIndex
51 | zIndex: 1,
52 |
53 | // set the trigger selector
54 | trigger: null,
55 |
56 | // offset position
57 | offset: [0, 1],
58 |
59 | // override class
60 | customClass: '',
61 |
62 | // set display view, optional date or month
63 | view: 'date',
64 |
65 | // set current date
66 | date: new Date(),
67 |
68 | // date format
69 | format: 'yyyy/mm/dd',
70 |
71 | // first day of the week
72 | startWeek: 0,
73 |
74 | // week format
75 | weekArray: ['日', '一', '二', '三', '四', '五', '六'],
76 |
77 | // month format
78 | monthArray: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
79 |
80 | // optional date range
81 | // value: `[ start date[, end date] ]`
82 | // Fixed date range: [new Date(2016, 0, 1), new Date(2016, 11, 31)] or ['2016/1/1', '2016/12/1']
83 | // Starting today: [new Date(), null] or [new Date()]
84 | selectedRang: null,
85 |
86 | // display data when mouse hover
87 | // value: `[{ date: String || Date, value: object }, ... ]`
88 | // example: [ { date: '2016/1/1', value: 'A new Year'} ] or [ { date: new Date(), value: 'What to do'} ]
89 | data: null,
90 |
91 | // data label format
92 | // No display is set to `false`
93 | label: '{d}\n{v}',
94 |
95 | // arrow characters
96 | prev: '<',
97 | next: '>',
98 |
99 | // callback function when view changed
100 | // params: view, y, m
101 | viewChange: $.noop,
102 |
103 | // callback function when selected
104 | onSelected: function (view, date, value) {
105 | // body...
106 | },
107 |
108 | // callback function when mouseenter
109 | onMouseenter: $.noop,
110 |
111 | // callback function when closed
112 | onClose: $.noop
113 | }
114 | ```
115 |
116 | # Methods
117 |
118 | ```js
119 | $element.calendar(method, value)
120 | ```
121 |
122 | * `setDate`:Setting selected date.
123 | * `setData`:Setting hover data.
--------------------------------------------------------------------------------
/dist/calendar.css:
--------------------------------------------------------------------------------
1 | .calendar-label,.calendar-label i{display:none;left:50%;position:absolute}.calendar{width:280px;height:330px}.calendar-modal{display:none;position:absolute;background:#fdfdfd;border:1px solid #e8e8e8}.calendar-modal .view{box-shadow:1px 2px 3px #ddd}.calendar-inner{position:relative;z-index:1;-webkit-perspective:1000;-moz-perspective:1000;perspective:1000;-webkit-transform:perspective(1000px);transform:perspective(1000px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.calendar-views{-webkit-transform-style:preserve-3d;transform-style:preserve-3d;width:100%}.calendar .view{-webkit-backface-visibility:hidden;backface-visibility:hidden;position:absolute;top:0;left:0;background:#fff;-webkit-transition:.6s;transition:.6s}.calendar-d .view-month,.calendar-m .view-date{-webkit-transform:rotateY(180deg);transform:rotateY(180deg);visibility:hidden;z-index:1}.calendar-d .view-date,.calendar-m .view-month{-webkit-transform:rotateY(0);transform:rotateY(0);visibility:visible;z-index:2}.calendar-ct,.calendar-hd,.calendar-views .days,.calendar-views .week{overflow:hidden}.calendar .date-items li,.calendar .view,.calendar-arrow .prev,.calendar-display{float:left}.calendar-arrow,.calendar-arrow .next{float:right}.calendar-ct{height:280px}.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.new[disabled],.calendar li.old[disabled]{color:#eee}.calendar-arrow span:hover,.calendar-display .m,.calendar-display:hover,.calendar-views .week{color:#888}.calendar-views .days .new,.calendar-views .days .old{color:#ccc}.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 .days li,.calendar .week 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:#66be8c!important}.calendar .calendar-views .selected{color:#66be8c;background:#CDE9D9!important}.calendar .calendar-views .dot{position:absolute;left:50%;bottom:4px;margin-left:-2px;width:4px;height:4px;background:#66be8c;border-radius:50%}.calendar-views .now .dot{background:#fff}.calendar .date-items{width:300%;margin-left:-100%}.calendar-label{top: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{bottom:-12px;width:0;height:0;margin-left:-3px;border:6px solid transparent;border-top-color:#000}
--------------------------------------------------------------------------------
/dist/calendar.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @authors yusen
3 | * @date 2017-01-04 21:34:19
4 | * @github https://github.com/yscoder/Calendar
5 | */
6 | !function(t,e){"function"==typeof define&&define.amd?define("calendar",["jquery"],e):"object"==typeof exports?module.exports=e(require("jquery")):e(t.jQuery)}(this,function(t){function e(t){return"[object Date]"===V.call(t)}function a(t){return"[object String]"===V.call(t)}function i(t){return t.getAttribute("class")||t.getAttribute("className")}function n(e,a){this.$element=t(e),this.options=t.extend({},t.fn.calendar.defaults,a),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()}var s={width:280,height:280,zIndex:1,trigger:null,offset:[0,1],customClass:"",view:"date",date:new Date,format:"yyyy/mm/dd",startWeek:0,weekArray:["日","一","二","三","四","五","六"],monthArray:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],selectedRang:null,data:null,label:"{d}\n{v}",prev:"<",next:">",viewChange:t.noop,onSelected:function(t,e,a){},onMouseenter:t.noop,onClose:t.noop},o="data-calendar-",r="["+o+"display-date]",h="["+o+"display-month]",l="["+o+"arrow-date]",d="["+o+"arrow-month]",c=o+"day",u=o+"month",p="disabled",m="markData",f={date:"calendar-d",month:"calendar-m"},g="old",v="new",D="now",w="selected",y='',M='{year}/{month}',$='style="width:{w}px;height:{h}px;line-height:{h}px"',x="{wk}",b="{value}',I="{m}",k=['','
','
",'
','
','
{yyyy}','
','{prev}','{next}',"
","
",'
{month}
',"
","
","
",''],V=Object.prototype.toString;String.prototype.repeat=function(t){return this.replace(/\{\w+\}/g,function(e){var a=e.replace(/\{|\}/g,"");return t[a]||""})},String.prototype.toDate=function(){var t=(new Date,this.replace(/\d/g,"").charAt(0)),e=this.split(t);return new Date(parseInt(e[0]),parseInt(e[1])-1,parseInt(e[2]))},Date.prototype.format=function(t){var e=this.getFullYear(),a=this.getMonth()+1,i=this.getDate();return t.replace("yyyy",e).replace("mm",a).replace("dd",i)},Date.prototype.isSame=function(t,a,i){if(e(t)){var n=t;t=n.getFullYear(),a=n.getMonth()+1,i=n.getDate()}return this.getFullYear()===t&&this.getMonth()+1===a&&this.getDate()===i},Date.prototype.add=function(t){this.setDate(this.getDate()+t)},Date.prototype.minus=function(t){this.setDate(this.getDate()-t)},Date.prototype.clearTime=function(t){return this.setHours(0),this.setSeconds(0),this.setMinutes(0),this.setMilliseconds(0),this},Date.isLeap=function(t){return t%100!==0&&t%4===0||t%400===0},Date.getDaysNum=function(t,e){var a=31;switch(e){case 2:a=this.isLeap(t)?29:28;break;case 4:case 6:case 9:case 11:a=30}return a},Date.getSiblingsMonth=function(t,e,a){var i=new Date(t,e-1);return i.setMonth(e-1+a),{y:i.getFullYear(),m:i.getMonth()+1}},Date.getPrevMonth=function(t,e,a){return this.getSiblingsMonth(t,e,0-(a||1))},Date.getNextMonth=function(t,e,a){return this.getSiblingsMonth(t,e,a||1)},Date.tryParse=function(t){return t?e(t)?t:t.toDate():t},n.prototype={constructor:n,getDayAction:function(t){var e=c;if(this.selectedRang){var a=Date.tryParse(this.selectedRang[0]),i=Date.tryParse(this.selectedRang[1]);(a&&ti.clearTime())&&(e=p)}return e},getDayData:function(t){var e,a=this.data;if(a)for(var i=0,n=a.length;i'));e(a)?(n=a.getFullYear(),s=a.getMonth()+1):(n=Number(a),s=Number(i)),o=new Date(n,s-1,1).getDay()||7,l=o-this.options.startWeek,r=Date.getDaysNum(n,s),h=Date.getPrevMonth(n,s),prevDaysNum=Date.getDaysNum(n,h.m),nextM=Date.getNextMonth(n,s);for(var c=1,u=2,p=3,m=0,f=prevDaysNum-l+1;f<=prevDaysNum;f++,m++)d.append(this.getDayItem(h.y,h.m,f,c));for(var g=1;g<=r;g++,m++)d.append(this.getDayItem(n,s,g,u));for(var v=1,D=42-m;v<=D;v++)d.append(this.getDayItem(nextM.y,nextM.m,v,p));return t("").width(this.options.width).append(d)},getWeekHtml:function(){for(var t=[],e=this.options.weekArray,a=this.options.startWeek,i=e.length,n=this.width/7,s=this.height/7,o=a;o"));var s=n.outerWidth(),o=n.outerHeight();n.css({left:t.pageX-s/2+"px",top:t.pageY-o-20+"px",zIndex:this.options.zIndex+1}).show()},hasLabel:function(){return!!this.options.label&&(t("body").append(this.$label),!0)},event:function(){var e=this,a=e.options.viewChange;e.$element.on("click",r,function(){var t=e.getDisDateValue();e.updateMonthView(t[0],t[1]),a("month",t[0],t[1])}).on("click",h,function(){var t=this.innerHTML;e.updateDateView(t),a("date",t)}),e.$element.on("click",l,function(){var t=e.getDisDateValue(),n=i(this),s=t[0],o=t[1],r=e.updateDateView(s,o,n,function(){a("date",r.y,r.m)})}).on("click",d,function(){var t=Number(e.$disMonth.html()),n=i(this);t="prev"===n?t-1:t+1,e.updateMonthView(t),a("month",t)}),e.$element.on("click","["+c+"]",function(){var a=parseInt(this.innerHTML),n=i(this),s=/new|old/.test(n)?n.match(/new|old/)[0]:"",o=e.selectedDay(a,s);e.options.onSelected.call(this,"date",o,t(this).data(m)),e.$trigger&&e.hide("date",o,t(this).data(m))}).on("click","["+u+"]",function(){var i=Number(e.$disMonth.html()),n=t(this).index()+1;e.updateDateView(i,n),a("date",i,n),e.options.onSelected.call(this,"month",new Date(i,n-1))}),e.$element.on("mouseenter","["+c+"]",function(a){var i=t(this),n=i.attr(c).toDate();e.hasLabel()&&i.data(m)&&e.showLabel(a,"date",n,i.data(m)),e.options.onMouseenter.call(this,"date",n,i.data(m))}).on("mouseleave","["+c+"]",function(){e.$label.hide()})},resize:function(){var t=this.width,e=this.height,a=this.$element.find(".calendar-hd").outerHeight();this.$element.width(t).height(e+a).find(".calendar-inner, .view").css("width",t+"px"),this.$element.find(".calendar-ct").width(t).height(e)},init:function(){this.fillStatic(),this.resize(),this.render(),this.view=this.options.view,this.setView(this.view),this.event()},setData:function(t){if(this.data=t,"date"===this.view){var e=this.getDisDateValue();this.fillDateItems(e[0],e[1])}else"month"===this.view&&this.updateMonthView(this.$disMonth.html())},setDate:function(t){var e=Date.tryParse(t);this.updateDateView(e.getFullYear(),e.getMonth()+1),this.selectedDay(e.getDate())},methods:function(t,e){if("[object Function]"===V.call(this[t]))return this[t].apply(this,e)}},t.fn.calendar=function(e){var i,s=this.data("calendar"),o=[].slice.call(arguments);return s?a(e)?(i=e,o.shift(),s.methods(i,o)):this:this.each(function(){return t(this).data("calendar",new n(this,e))})},t.fn.calendar.defaults=s});
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp')
2 | const cleanCss = require('gulp-clean-css')
3 | const uglify = require('gulp-uglify')
4 | const browserSync = require('browser-sync')
5 |
6 | const src = './src/'
7 | const dist = './dist/'
8 |
9 | gulp.task('css', () => {
10 | return gulp.src(src + '*.css')
11 | .pipe(cleanCss())
12 | .pipe(gulp.dest(dist))
13 | })
14 |
15 | gulp.task('js', () => {
16 | return gulp.src(src + '*.js')
17 | .pipe(uglify({
18 | preserveComments: 'license'
19 | }))
20 | .pipe(gulp.dest(dist))
21 | })
22 |
23 | gulp.task('default', ['css', 'js'])
24 |
25 | gulp.task('dev', () => {
26 | browserSync.init({
27 | server: {
28 | baseDir: './'
29 | }
30 | })
31 |
32 | gulp.watch(['index.html', src + '*.*'], browserSync.reload)
33 | })
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Calendar demo
8 |
9 |
52 |
53 |
54 |
55 |
Inline calendar
56 |
57 |
58 |
Trigger calendar
59 |
60 |
61 |
62 |
63 |
64 |
65 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flip-calendar-picker",
3 | "version": "1.3.0",
4 | "description": "A calendar component, based on jQuery.",
5 | "main": "./src/calendar.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/yscoder/Calendar.git"
9 | },
10 | "keywords": [
11 | "jquery",
12 | "calendar",
13 | "picker",
14 | "date"
15 | ],
16 | "author": "yusen",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/yscoder/Calendar/issues"
20 | },
21 | "homepage": "https://github.com/yscoder/Calendar#readme",
22 | "devDependencies": {
23 | "browser-sync": "^2.18.2",
24 | "gulp": "^3.9.1",
25 | "gulp-clean-css": "^2.0.13",
26 | "gulp-uglify": "^2.0.0"
27 | }
28 | }
--------------------------------------------------------------------------------
/src/calendar.css:
--------------------------------------------------------------------------------
1 | .calendar {
2 | width: 280px;
3 | height: 330px;
4 | }
5 |
6 | .calendar-modal {
7 | display: none;
8 | position: absolute;
9 | background: #fdfdfd;
10 | border: 1px solid #e8e8e8;
11 | }
12 |
13 | .calendar-modal .view {
14 | box-shadow: 1px 2px 3px #ddd;
15 | }
16 |
17 | .calendar-inner {
18 | position: relative;
19 | z-index: 1;
20 | -webkit-perspective: 1000;
21 | -moz-perspective: 1000;
22 | perspective: 1000;
23 | -webkit-transform: perspective(1000px);
24 | transform: perspective(1000px);
25 | -webkit-transform-style: preserve-3d;
26 | transform-style: preserve-3d;
27 | }
28 |
29 | .calendar-views {
30 | -webkit-transform-style: preserve-3d;
31 | transform-style: preserve-3d;
32 | }
33 |
34 | .calendar .view {
35 | -webkit-backface-visibility: hidden;
36 | backface-visibility: hidden;
37 | position: absolute;
38 | top: 0;
39 | left: 0;
40 | background: #fff;
41 | *overflow: hidden;
42 | -webkit-transition: .6s;
43 | transition: .6s;
44 | }
45 |
46 | .calendar-d .view-month,
47 | .calendar-m .view-date {
48 | -webkit-transform: rotateY(180deg);
49 | transform: rotateY(180deg);
50 | visibility: hidden;
51 | z-index: 1;
52 | }
53 |
54 | .calendar-d .view-date,
55 | .calendar-m .view-month {
56 | -webkit-transform: rotateY(0deg);
57 | transform: rotateY(0deg);
58 | visibility: visible;
59 | z-index: 2;
60 | }
61 |
62 | .calendar-ct,
63 | .calendar-hd,
64 | .calendar-views .week,
65 | .calendar-views .days {
66 | overflow: hidden;
67 | }
68 |
69 | .calendar-views {
70 | width: 100%;
71 | }
72 |
73 | .calendar .view,
74 | .calendar-display,
75 | .calendar-arrow .prev,
76 | .calendar .date-items li {
77 | float: left;
78 | }
79 |
80 | .calendar-arrow,
81 | .calendar-arrow .next {
82 | float: right;
83 | }
84 |
85 | .calendar-ct {
86 | height: 280px;
87 | }
88 |
89 | .calendar-hd {
90 | padding: 10px 0;
91 | height: 30px;
92 | line-height: 30px;
93 | }
94 |
95 | .calendar-display {
96 | font-size: 28px;
97 | text-indent: 10px;
98 | }
99 |
100 | .view-month .calendar-hd {
101 | padding: 10px;
102 | }
103 |
104 | .calendar-arrow,
105 | .calendar-display {
106 | color: #ddd;
107 | }
108 |
109 | .calendar li[disabled] {
110 | color: #bbb;
111 | }
112 |
113 | .calendar li.old[disabled],
114 | .calendar li.new[disabled] {
115 | color: #eee;
116 | }
117 |
118 | .calendar-display .m,
119 | .calendar-views .week,
120 | .calendar-display:hover,
121 | .calendar-arrow span:hover {
122 | color: #888;
123 | }
124 |
125 | .calendar-views .days .old,
126 | .calendar-views .days .new {
127 | color: #ccc
128 | }
129 |
130 | .calendar-arrow span,
131 | .calendar-views .days li[data-calendar-day],
132 | .calendar-views .view-month li[data-calendar-month] {
133 | cursor: pointer;
134 | }
135 |
136 | .calendar li[disabled] {
137 | cursor: not-allowed;
138 | }
139 |
140 | .calendar-arrow {
141 | width: 50px;
142 | margin-right: 10px;
143 | }
144 |
145 | .calendar-arrow span {
146 | font: 500 26px sans-serif;
147 | }
148 |
149 | .calendar ol li {
150 | position: relative;
151 | float: left;
152 | text-align: center;
153 | border-radius: 50%;
154 | }
155 |
156 | .calendar .week li,
157 | .calendar .days li {
158 | width: 40px;
159 | height: 40px;
160 | line-height: 40px;
161 | }
162 |
163 | .calendar .month-items li {
164 | width: 70px;
165 | height: 70px;
166 | line-height: 70px;
167 | }
168 |
169 | .calendar .days li[data-calendar-day]:hover,
170 | .calendar .view-month li[data-calendar-month]:hover {
171 | background: #eee;
172 | }
173 |
174 | .calendar .calendar-views .now {
175 | color: #fff;
176 | background: #66be8c!important;
177 | }
178 |
179 | .calendar .calendar-views .selected {
180 | color: #66be8c;
181 | background: #CDE9D9!important;
182 | }
183 |
184 | .calendar .calendar-views .dot {
185 | position: absolute;
186 | left: 50%;
187 | bottom: 4px;
188 | margin-left: -2px;
189 | width: 4px;
190 | height: 4px;
191 | background: #66be8c;
192 | border-radius: 50%;
193 | }
194 |
195 | .calendar-views .now .dot {
196 | background: #fff;
197 | }
198 |
199 | .calendar .date-items {
200 | width: 300%;
201 | margin-left: -100%;
202 | }
203 |
204 | .calendar-label {
205 | display: none;
206 | position: absolute;
207 | top: 50%;
208 | left: 50%;
209 | z-index: 2;
210 | padding: 5px 10px;
211 | line-height: 22px;
212 | color: #fff;
213 | background: #000;
214 | border-radius: 3px;
215 | opacity: .7;
216 | filter: alpha(opacity=70);
217 | }
218 |
219 | .calendar-label i {
220 | display: none;
221 | position: absolute;
222 | left: 50%;
223 | bottom: -12px;
224 | width: 0;
225 | height: 0;
226 | margin-left: -3px;
227 | border: 6px solid transparent;
228 | border-top-color: #000;
229 | }
230 |
--------------------------------------------------------------------------------
/src/calendar.js:
--------------------------------------------------------------------------------
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 = '',
119 | DATE_DIS_TPL = '{year}/{month}',
120 |
121 | ITEM_STYLE = 'style="width:{w}px;height:{h}px;line-height:{h}px"',
122 | WEEK_ITEM_TPL = '{wk}',
123 | DAY_ITEM_TPL = '{value}',
124 | MONTH_ITEM_TPL = '{m}',
125 |
126 | TEMPLATE = [
127 | '',
128 | '
',
129 | '
',
130 | '
',
139 | '
',
140 | '
{week}
',
141 | '
',
142 | '
',
143 | '
',
144 | '
',
145 | '
',
146 | '
{yyyy}',
147 | '
',
148 | '{prev}',
149 | '{next}',
150 | '
',
151 | '
',
152 | '
{month}
',
153 | '
',
154 | '
',
155 | '
',
156 | ''
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 = $('
');
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 $('').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, '
'));
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 | }));
804 |
--------------------------------------------------------------------------------