├── README.md ├── week-picker-view.css ├── index.html └── week-picker.js /README.md: -------------------------------------------------------------------------------- 1 | # [week-picker](http://scripts.morshed-alam.com/week-picker/) 2 | 3 | A multi month week picker calendar. 4 | 5 | [Demo](http://scripts.morshed-alam.com/week-picker/) 6 | 7 | 8 | # Example 9 | 10 | With default options: 11 | 12 | ```html 13 | 14 | ``` 15 | 16 | Specifying number of months via data tag: 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | Specifying start of week via data tag: 23 | 24 | ```html 25 | 26 | ``` 27 | 28 | Specifying target field using icon: 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | ## Data Options 38 | * Default week start: 0 [0 - Sunday to 6 - Saturday] 39 | * Default Number of Weeks: 6 40 | * Target field: self [Can parameterize using data-target="#field_id"] 41 | 42 | ## TO DO 43 | * Date format support 44 | * Theme support 45 | * Language support 46 | * Inline calendar -------------------------------------------------------------------------------- /week-picker-view.css: -------------------------------------------------------------------------------- 1 | .weekpicker { 2 | background-color: #ffffff; 3 | border: 1px solid #006DCC; 4 | -webkit-border-radius: 4px; 5 | -moz-border-radius: 4px; 6 | border-radius: 4px; 7 | -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 8 | -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 9 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 10 | -webkit-background-clip: padding-box; 11 | -moz-background-clip: padding-box; 12 | background-clip: padding-box; 13 | display: none; 14 | position: absolute; 15 | z-index: 900; 16 | padding-bottom: 4px; 17 | font-size: 10px; 18 | } 19 | 20 | .weekpicker .head td, .weekpicker td.span0, .weekpicker .calendar .span1 { 21 | color: #006DCC; 22 | } 23 | 24 | .weekpicker .week-nav { 25 | font-weight: bold; 26 | text-align: center; 27 | padding: 4px 0; 28 | font-size: 130%; 29 | } 30 | 31 | .weekpicker .week-nav .prev, .weekpicker .week-nav .next { 32 | cursor: pointer; 33 | } 34 | 35 | .weekpicker .calendar { 36 | padding: 5px; 37 | } 38 | 39 | .weekpicker .calendar td { 40 | padding: 0px 5px; 41 | text-align: center; 42 | vertical-align: middle; 43 | } 44 | 45 | .weekpicker .calendar td.month-col { 46 | vertical-align: top; 47 | } 48 | 49 | .weekpicker .week .box0, .weekpicker .week .box1 { 50 | cursor: pointer; 51 | } 52 | 53 | .weekpicker .week-label { 54 | width: 20px; 55 | padding: 0 5px; 56 | font-weight: bold; 57 | } 58 | 59 | .weekpicker .calendar .box0 { 60 | background-color: #F7F7F7; 61 | color: #A19E98; 62 | } 63 | 64 | .weekpicker .calendar .box1 { 65 | background-color: #FFFFFF; 66 | color: #A19E98; 67 | } 68 | 69 | .weekpicker td.span0, .weekpicker .calendar .span1 { 70 | border-top: 1px solid #E7E7E7; 71 | border-left: 1px solid #E7E7E7; 72 | border-bottom: 1px solid #E7E7E7; 73 | } 74 | 75 | .weekpicker .calendar .box0, .weekpicker .calendar .box1 { 76 | border: 1px solid #E7E7E7; 77 | } 78 | 79 | .weekpicker .highlighted .box0, .weekpicker .highlighted .box1 { 80 | background-image: -webkit-linear-gradient(#D2D2D2, #c0c0c0, #c0c0c0); 81 | background-color: #D2D2D2; 82 | color: #7F7E7C; 83 | } 84 | 85 | .weekpicker .week-nav, 86 | .weekpicker .selected .box0, 87 | .weekpicker .selected .box1 { 88 | background-color: #006DCC; 89 | color: #ffffff; 90 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 91 | background-image: -webkit-linear-gradient(top, #08C, #04C); 92 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Multi Month Week Picker 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

Multi month week picker

18 | Repository 19 | Download 20 |

21 | 22 | 23 |

Example

24 | 25 |

A field with default options.

26 | 27 |
28 | 29 | Markup: 30 | <input data-weekpicker="weekpicker"/> 31 |
32 | 33 |

Specifying number of months via data tag.

34 | 35 |
36 | 37 | Markup: 38 | <input data-weekpicker="weekpicker" data-months="5"/> 39 |
40 | 41 |

Specifying start of week via data tag.

42 | 43 |
44 | 45 | Markup: 46 | <input data-weekpicker="weekpicker" data-week_start="1"/> 47 |
48 | 49 |

Using Icon.

50 | 51 |
52 |
53 | 54 | 56 |
57 | 58 | Markup: 59 | <input type="text" readonly="" id="week_range"/> 60 | <span class="add-on" data-weekpicker="weekpicker" data-target="#week_range"> 61 | <i class="icon-th"></i> 62 | </span> 63 |
64 |
65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /week-picker.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var selector = '[data-weekpicker]', all = []; 3 | 4 | function clearWeekPickers(except) { 5 | var ii; 6 | for (ii = 0; ii < all.length; ii++) { 7 | if (all[ii] != except) { 8 | all[ii].hide(); 9 | } 10 | } 11 | } 12 | 13 | function WeekPicker(element, options) { 14 | this.$el = $(element); 15 | this.proxy('show').proxy('ahead').proxy('hide').proxy('keyHandler').proxy('selectWeek'); 16 | var options = $.extend({}, $.fn.weekpicker.defaults, options); 17 | 18 | if ((!!options.parse) || (!!options.format) || !this.detectNative()) { 19 | $.extend(this, options); 20 | this.$el.data('weekpicker', this); 21 | all.push(this); 22 | this.init(); 23 | } 24 | } 25 | 26 | WeekPicker.prototype = { 27 | detectNative:function (el) { 28 | if (navigator.userAgent.match(/(iPad|iPhone); CPU(\ iPhone)? OS 5_\d/i)) { 29 | // jQuery will only change the input type of a detached element. 30 | var $marker = $('').insertBefore(this.$el); 31 | this.$el.detach().attr('type', 'date').insertAfter($marker); 32 | $marker.remove(); 33 | 34 | return true; 35 | } 36 | 37 | return false; 38 | }, 39 | 40 | init:function () { 41 | this.setParams(); 42 | var $nav = $('
').addClass('week-nav').append(this.nav(this.months)); 43 | 44 | this.calculateDates(); 45 | 46 | $calendar = $("").addClass('calendar'); 47 | this.$months = $(''); 48 | $calendar.append(this.$months); 49 | 50 | this.$picker = $('
') 51 | .click(function (e) { 52 | e.stopPropagation(); 53 | }) 54 | // Use this to prevent accidental text selection. 55 | .mousedown(function (e) { 56 | e.preventDefault(); 57 | }) 58 | .addClass('weekpicker') 59 | .append($nav, $calendar) 60 | .insertAfter(this.$el); 61 | 62 | this.$el 63 | .focus(this.show) 64 | .click(this.show) 65 | .change($.proxy(function () { 66 | this.selectWeek(); 67 | }, this)); 68 | 69 | this.selectWeek(); 70 | this.hide(); 71 | }, 72 | 73 | setParams:function () { 74 | var data; 75 | 76 | data = this.$el.attr('data-target'); 77 | this.$target = data ? $(data) : this.$el; 78 | 79 | data = parseInt(this.$el.attr('data-months')); 80 | this.months = data ? data : this.months; 81 | 82 | data = parseInt(this.$el.attr('data-week_start')); 83 | this.week_start = data ? data : this.week_start; 84 | }, 85 | 86 | selectWeek:function (week) { 87 | if (typeof(week) == "undefined") { 88 | week = this.$target.val(); 89 | } 90 | 91 | this.$months.empty(); 92 | this.renderView(); 93 | 94 | $('.week', this.$months).click($.proxy(function (e) { 95 | this.update($(e.target).parent().attr("date")); 96 | }, this)).hover(function (e) { 97 | $('.highlighted', this.$months).removeClass('highlighted'); 98 | $(this).addClass('highlighted'); 99 | }); 100 | 101 | $('.selected', this.$months).removeClass('selected'); 102 | $('[date="' + week + '"]', this.$months).addClass('selected'); 103 | }, 104 | 105 | renderView:function () { 106 | var dates = [ 107 | [this.rangeStart(this.start), this.mid, 999], 108 | [this.mid_next, this.end, 999] 109 | ], 110 | thisDay, prevMonth = -1, weekEnd, tmp, 111 | row, col, label, firstDay, box_class; 112 | 113 | for (var c = 0; c < (this.months <= 1 ? 1 : 2); c++) { 114 | var m = -1, w = 0; 115 | 116 | dates[c][2] = this.daysBetween(dates[c][0], dates[c][1]); 117 | 118 | $col = $('
').addClass('month-col'); 119 | $monthDays = $(''); 120 | 121 | //Render head 122 | $head = $("").addClass('head'); 123 | $head.append($('').addClass('week'); 140 | 141 | //Print month label 142 | if (thisDay.getMonth() != prevMonth) { 143 | label = $('
')); 124 | for (var i = 0; i < this.shortDayNames.length; i++) { 125 | tmp = $('').addClass('dow'); 126 | tmp.text(this.shortDayNames[(i + this.week_start) % 7]); 127 | $head.append(tmp); 128 | } 129 | $monthDays.append($head); 130 | 131 | //Render date cells with month label 132 | for (var d = 0; d <= dates[c][2]; d++) { 133 | thisDay = this.incrementDay(dates[c][0], d); 134 | box_class = 'box' + (thisDay.getMonth() % 2); 135 | 136 | //Creating and append week in to month 137 | if (this.week_start == thisDay.getDay()) { 138 | firstDay = thisDay; 139 | row = $('
'); 144 | 145 | tmp = this.incrementDay(thisDay, 6); 146 | if (tmp.getMonth() == thisDay.getMonth()) { 147 | tmp = this.incrementDay(thisDay, 28); 148 | tmp = (tmp.getMonth() == thisDay.getMonth() ? 5 : 4); 149 | label.attr('rowspan', tmp); 150 | label.text(this.monthNames[thisDay.getMonth()] + '\n' 151 | + thisDay.getFullYear().toString().substring(2, 4)); 152 | label.addClass('week-label ' + box_class.replace('box', 'span')); 153 | } 154 | 155 | row.append(label); 156 | } 157 | 158 | //Set week range 159 | weekEnd = this.incrementDay(thisDay, 6); 160 | row.attr('date', this.formatWeek(thisDay, weekEnd)); 161 | 162 | if (prevMonth != thisDay.getMonth()) { 163 | m = m + 1; 164 | prevMonth = thisDay.getMonth(); 165 | } 166 | } 167 | 168 | //Append Day in to Week 169 | col = $('').attr('date', this.format(thisDay)); 170 | col.text(thisDay.getDate()); 171 | col.addClass(box_class); 172 | row.append(col); 173 | 174 | if (d % 7 == 0) { 175 | $monthDays.append(row); 176 | } 177 | } 178 | 179 | $col.append($monthDays); 180 | 181 | this.$months.append($col); 182 | } 183 | }, 184 | 185 | proxy:function (meth) { 186 | this[meth] = $.proxy(this[meth], this); 187 | return this; 188 | }, 189 | 190 | daysBetween:function (start, end) { 191 | var start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate()); 192 | var end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate()); 193 | 194 | return (end - start) / 86400000; 195 | }, 196 | 197 | findClosest:function (dow, date, direction) { 198 | var difference = direction * (Math.abs(date.getDay() - dow - (direction * 7)) % 7); 199 | return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference); 200 | }, 201 | 202 | rangeStart:function (date) { 203 | return this.findClosest(this.week_start, 204 | new Date(date.getFullYear(), date.getMonth()), -1); 205 | }, 206 | 207 | rangeEnd:function (date) { 208 | return this.findClosest((this.week_start - 1) % 7, 209 | new Date(date.getFullYear(), date.getMonth() + 1, 0), 1); 210 | }, 211 | 212 | update:function (s) { 213 | this.$target.val(s); 214 | this.$el.val(s).change(); 215 | }, 216 | 217 | show:function (e) { 218 | e && e.stopPropagation(); 219 | 220 | clearWeekPickers(this); 221 | 222 | var offset = this.$el.offset(); 223 | 224 | this.$picker.css({ 225 | top:offset.top + this.$el.outerHeight() + 2, 226 | left:offset.left 227 | }).show(); 228 | 229 | $('html').on('keydown', this.keyHandler); 230 | }, 231 | 232 | hide:function () { 233 | this.$picker.hide(); 234 | $('html').off('keydown', this.keyHandler); 235 | }, 236 | 237 | keyHandler:function (e) { 238 | // Keyboard navigation shortcuts. 239 | switch (e.keyCode) { 240 | case 9: 241 | case 27: 242 | case 13: 243 | this.hide(); 244 | break; 245 | default: 246 | return; 247 | } 248 | e.preventDefault(); 249 | }, 250 | 251 | parse:function (s) { 252 | // Parse a partial RFC 3339 string into a Date. 253 | var m; 254 | if ((m = s.match(/^(\d{2,2})\/(\d{2,2})\/(\d{2,2})$/))) { 255 | return new Date(m[0], m[1] - 1, m[2]); 256 | } else { 257 | return null; 258 | } 259 | }, 260 | 261 | format:function (date) { 262 | // Format a Date into a string as specified by RFC 3339. 263 | var month = (date.getMonth() + 1).toString(), 264 | dom = date.getDate().toString(); 265 | if (month.length === 1) { 266 | month = '0' + month; 267 | } 268 | 269 | if (dom.length === 1) { 270 | dom = '0' + dom; 271 | } 272 | 273 | return month + "/" + dom + '/' + date.getFullYear().toString().substr(2, 2); 274 | }, 275 | 276 | formatWeek:function (from, to) { 277 | return this.format(from) + '-' + this.format(to); 278 | }, 279 | 280 | nav:function (months) { 281 | var $subnav = $('
' + 282 | '← Previous | ' + 283 | ' Next →' + 284 | '
'); 285 | 286 | $('.prev', $subnav).click($.proxy(function () { 287 | this.ahead(-months) 288 | }, this)); 289 | $('.next', $subnav).click($.proxy(function () { 290 | this.ahead(months) 291 | }, this)); 292 | 293 | return $subnav; 294 | }, 295 | 296 | incrementDay:function (d, i) { 297 | return new Date(d.getFullYear(), d.getMonth(), d.getDate() + i, 12, 00); 298 | }, 299 | 300 | ahead:function (months) { 301 | var date = new Date(this.start.getFullYear(), this.start.getMonth() + months, 1); 302 | this.calculateDates(date); 303 | this.selectWeek(); 304 | }, 305 | 306 | calculateDates:function (date) { 307 | this.start = date ? date : new Date(); 308 | this.mid = this.rangeEnd(new Date(this.start.getFullYear(), this.start.getMonth() + Math.ceil(this.months / 2), 0)); 309 | this.mid_next = new Date(this.mid.getFullYear(), this.mid.getMonth(), this.mid.getDate() + 1); 310 | this.end = this.rangeEnd(new Date(this.start.getFullYear(), this.start.getMonth() + this.months, 0)); 311 | } 312 | }; 313 | 314 | /* WEEK PICKER DEFINITION 315 | * ============================ */ 316 | $(document).ready(function () { 317 | $.fn.weekpicker = function (options) { 318 | return this.each(function () { 319 | new WeekPicker(this, options); 320 | }); 321 | }; 322 | 323 | $(function () { 324 | $(selector).weekpicker(); 325 | $('html').click(clearWeekPickers); 326 | }); 327 | 328 | $.fn.weekpicker.WeekPicker = WeekPicker; 329 | 330 | $.fn.weekpicker.defaults = { 331 | monthNames:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 332 | shortDayNames:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], 333 | week_start:0, 334 | months:6 335 | }; 336 | 337 | }); 338 | }()); 339 | --------------------------------------------------------------------------------