1/** 2 * Bootstrap based calendar full view. 3 * 4 * https://github.com/Serhioromano/bootstrap-calendar 5 * 6 * User: Sergey Romanov <[email protected]> 7 */ 8"use strict"; 9 10Date.prototype.getWeek = function() { 11 var onejan = new Date(this.getFullYear(), 0, 1); 12 return Math.ceil((((this.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7); 13}; 14Date.prototype.getMonthFormatted = function() { 15 var month = this.getMonth() + 1; 16 return month < 10 ? '0' + month : month; 17}; 18Date.prototype.getDateFormatted = function() { 19 var date = this.getDate(); 20 return date < 10 ? '0' + date : date; 21}; 22if(!String.prototype.format) { 23 String.prototype.format = function() { 24 var args = arguments; 25 return this.replace(/{(\d+)}/g, function(match, number) { 26 return typeof args[number] != 'undefined' ? args[number] : match; 27 }); 28 }; 29} 30if(!String.prototype.formatNum) { 31 String.prototype.formatNum = function(decimal) { 32 var r = "" + this; 33 while(r.length < decimal) 34 r = "0" + r; 35 return r; 36 }; 37} 38 39 40(function($) { 41 42 var defaults = { 43 // Width of the calendar 44 width: '100%', 45 // Initial view (can be 'month', 'week', 'day') 46 view: 'month', 47 // Initial date. No matter month, week or day this will be a starting point. Can be 'now' or a date in format 'yyyy-mm-dd' 48 day: 'now', 49 // Day Start time and end time with time intervals. Time split 10, 15 or 30. 50 time_start: '06:00', 51 time_end: '22:00', 52 time_split: '30', 53 // Source of events data. It can be one of the following: 54 // - URL to return JSON list of events in special format. 55 // {success:1, result: [....]} or for error {success:0, error:'Something terrible happened'} 56 // events: [...] as described in events property description 57 // The start and end variables will be sent to this url 58 // - A function that received the start and end date, and that 59 // returns an array of events (as described in events property description) 60 // - An array containing the events 61 events_source: '', 62 // Path to templates should end with slash /. It can be as relative 63 // /component/bootstrap-calendar/tmpls/ 64 // or absolute 65 // http://localhost/component/bootstrap-calendar/tmpls/ 66 tmpl_path: 'Calendar/tmpls/', 67 tmpl_cache: true, 68 classes: { 69 months: { 70 inmonth: 'cal-day-inmonth', 71 outmonth: 'cal-day-outmonth', 72 saturday: 'cal-day-weekend', 73 sunday: 'cal-day-weekend', 74 holidays: 'cal-day-holiday', 75 today: 'cal-day-today' 76 }, 77 week: { 78 workday: 'cal-day-workday', 79 saturday: 'cal-day-weekend', 80 sunday: 'cal-day-weekend', 81 holidays: 'cal-day-holiday', 82 today: 'cal-day-today' 83 } 84 }, 85 // ID of the element of modal window. If set, events URLs will be opened in modal windows. 86 modal: null, 87 // modal handling setting, one of "iframe", "ajax" or "template" 88 modal_type: "iframe", 89 // function to set modal title, will be passed the event as a parameter 90 modal_title: null, 91 views: { 92 year: { 93 slide_events: 1, 94 enable: 1 95 }, 96 month: { 97 slide_events: 1, 98 enable: 1 99 }, 100 week: { 101 enable: 1 102 }, 103 day: { 104 enable: 1 105 } 106 }, 107 merge_holidays: false, 108 // ------------------------------------------------------------ 109 // CALLBACKS. Events triggered by calendar class. You can use 110 // those to affect you UI 111 // ------------------------------------------------------------ 112 onAfterEventsLoad: function(events) { 113 // Inside this function 'this' is the calendar instance 114 }, 115 onBeforeEventsLoad: function(next) { 116 // Inside this function 'this' is the calendar instance 117 next(); 118 }, 119 onAfterViewLoad: function(view) { 120 // Inside this function 'this' is the calendar instance 121 }, 122 onAfterModalShown: function(events) { 123 // Inside this function 'this' is the calendar instance 124 }, 125 onAfterModalHidden: function(events) { 126 // Inside this function 'this' is the calendar instance 127 }, 128 // ------------------------------------------------------------- 129 // INTERNAL USE ONLY. DO NOT ASSIGN IT WILL BE OVERRIDDEN ANYWAY 130 // ------------------------------------------------------------- 131 events: [], 132 templates: { 133 year: '', 134 month: '', 135 week: '', 136 day: '' 137 }, 138 stop_cycling: false 139 }; 140 141 var defaults_extended = { 142 first_day: 2, 143 holidays: { 144 // January 1 145 '01-01': "New Year's Day", 146 // Third (+3*) Monday (1) in January (01) 147 '01+3*1': "Birthday of Dr. Martin Luther King, Jr.", 148 // Third (+3*) Monday (1) in February (02) 149 '02+3*1': "Washington's Birthday", 150 // Last (-1*) Monday (1) in May (05) 151 '05-1*1': "Memorial Day", 152 // July 4 153 '04-07': "Independence Day", 154 // First (+1*) Monday (1) in September (09) 155 '09+1*1': "Labor Day", 156 // Second (+2*) Monday (1) in October (10) 157 '10+2*1': "Columbus Day", 158 // November 11 159 '11-11': "Veterans Day", 160 // Fourth (+4*) Thursday (4) in November (11) 161 '11+4*4': "Thanksgiving Day", 162 // December 25 163 '25-12': "Christmas" 164 } 165 }; 166 167 var strings = { 168 error_noview: 'Calendar: View {0}
168 not found', 169 error_dateformat: 'Calendar: Wrong date format {0}. Should be either "now" or "yyyy-mm-dd"', 170 error_loadurl: 'Calendar: Event URL is not set', 171 error_where: 'Calendar: Wrong navigation direction {0}. Can be only "next" or "prev" or "today"', 172 error_timedevide: 'Calendar: Time split parameter should divide 60 without decimals. Something like 10, 15, 30', 173 174 no_events_in_day: 'No events in this day.', 175 176 title_year: '{0}', 177 title_month: '{0} {1}', 178 title_week: 'week {0} of {1}', 179 title_day: '{0} {1} {2}, {3}', 180 181 week: 'Week {0}', 182 all_day: 'All day', 183 time: 'Time', 184 events: 'Events', 185 before_time: 'Ends before timeline', 186 after_time: 'Starts after timeline', 187 188 189 m0: 'January', 190 m1: 'February', 191 m2: 'March', 192 m3: 'April', 193 m4: 'May', 194 m5: 'June', 195 m6: 'July', 196 m7: 'August', 197 m8: 'September', 198 m9: 'October', 199 m10: 'November', 200 m11: 'December', 201 202 ms0: 'Jan', 203 ms1: 'Feb', 204 ms2: 'Mar', 205 ms3: 'Apr', 206 ms4: 'May', 207 ms5: 'Jun', 208 ms6: 'Jul', 209 ms7: 'Aug', 210 ms8: 'Sep', 211 ms9: 'Oct', 212 ms10: 'Nov', 213 ms11: 'Dec', 214 215 d0: 'Sun', 216 d1: 'Mon', 217 d2: 'Tue', 218 d3: 'Wed', 219 d4: 'Thu', 220 d5: 'Fri', 221 d6: 'Sat' 222 }; 223 224 var string = jQuery.extend(strings, tf_calendar.datepicker_opts); 225 226 var browser_timezone = ''; 227 try { 228 if($.type(window.jstz) == 'object' && $.type(jstz.determine) == 'function') { 229 browser_timezone = jstz.determine().name(); 230 if($.type(browser_timezone) !== 'string') { 231 browser_timezone = ''; 232 } 233 } 234 } 235 catch(e) { 236 } 237 238 function buildEventsUrl(events_url, data) { 239 var separator, key, url; 240 url = events_url; 241 separator = (events_url.indexOf('?') < 0) ? '?' : '&'; 242 for(key in data) { 243 url += separator + key + '=' + encodeURIComponent(data[key]); 244 separator = '&'; 245 } 246 return url; 247 } 248 249 function getExtentedOption(cal, option_name) { 250 var fromOptions = (cal.options[option_name] != null) ? cal.options[option_name] : null; 251 var fromLanguage = (cal.locale[option_name] != null) ? cal.locale[option_name] : null; 252 if((option_name == 'holidays') && cal.options.merge_holidays) { 253 var holidays = {}; 254 $.extend(true, holidays, fromLanguage ? fromLanguage : defaults_extended.holidays); 255 if(fromOptions) { 256 $.extend(true, holidays, fromOptions); 257 } 258 return holidays; 259 } 260 else { 261 if(fromOptions != null) { 262 return fromOptions; 263 } 264 if(fromLanguage != null) { 265 return fromLanguage; 266 } 267 return defaults_extended[option_name]; 268 } 269 } 270 271 function getHolidays(cal, year) { 272 var hash = []; 273 var holidays_def = getExtentedOption(cal, 'holidays'); 274 for(var k in holidays_def) { 275 hash.push(k + ':' + holidays_def[k]); 276 } 277 hash.push(year); 278 hash = hash.join('|'); 279 if(hash in getHolidays.cache) { 280 return getHolidays.cache[hash]; 281 } 282 var holidays = []; 283 $.each(holidays_def, function(key, name) { 284 var firstDay = null, lastDay = null, failed = false; 285 $.each(key.split('>'), function(i, chunk) { 286 var m, date = null; 287 if(m = /^(\d\d)-(\d\d)$/.exec(chunk)) { 288 date = new Date(year, parseInt(m[2], 10) - 1, parseInt(m[1], 10)); 289 } 290 else if(m = /^(\d\d)-(\d\d)-(\d\d\d\d)$/.exec(chunk)) { 291 if(parseInt(m[3], 10) == year) { 292 date = new Date(year, parseInt(m[2], 10) - 1, parseInt(m[1], 10)); 293 } 294 } 295 else if(m = /^easter(([+\-])(\d+))?$/.exec(chunk)) { 296 date = getEasterDate(year, m[1] ? parseInt(m[1], 10) : 0); 297 } 298 else if(m = /^(\d\d)([+\-])([1-5])\*([0-6])$/.exec(chunk)) { 299 var month = parseInt(m[1], 10) - 1; 300 var direction = m[2]; 301 var offset = parseInt(m[3]); 302 var weekday = parseInt(m[4]); 303 switch(direction) { 304 case '+': 305 var d = new Date(year, month, 1 - 7); 306 while(d.getDay() != weekday) { 307 d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1); 308 } 309 date = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 7 * offset); 310 break; 311 case '-': 312 var d = new Date(year, month + 1, 0 + 7); 313 while(d.getDay() != weekday) { 314 d = new Date(d.getFullYear(), d.getMonth(), d.getDate() - 1); 315 } 316 date = new Date(d.getFullYear(), d.getMonth(), d.getDate() - 7 * offset); 317 break; 318 } 319 } 320 if(!date) { 321 warn('Unknown holiday: ' + key); 322 failed = true; 323 return false; 324 } 325 switch(i) { 326 case 0: 327 firstDay = date; 328 break; 329 case 1: 330 if(date.getTime() <= firstDay.getTime()) { 331 warn('Unknown holiday: ' + key); 332 failed = true; 333 return false; 334 } 335 lastDay = date; 336 break; 337 default: 338 warn('Unknown holiday: ' + key); 339 failed = true; 340 return false; 341 } 342 }); 343 if(!failed) { 344 var days = []; 345 if(lastDay) { 346 for(var date = new Date(firstDay.getTime()); date.getTime() <= lastDay.getTime(); date.setDate(date.getDate() + 1)) { 347 days.push(new Date(date.getTime())); 348 } 349 } 350 else { 351 days.push(firstDay); 352 } 353 holidays.push({name: name, days: days}); 354 } 355 }); 356 getHolidays.cache[hash] = holidays; 357 return getHolidays.cache[hash]; 358 } 359 360 getHolidays.cache = {}; 361 362 function warn(message) { 363 if($.type(window.console) == 'object' && $.type(window.console.warn) == 'function') { 364 window.console.warn('[Bootstrap-Calendar] ' + message); 365 } 366 } 367 368 function Calendar(params, context) { 369 this.options = $.extend(true, {position: {start: new Date(), end: new Date()}}, defaults, params); 370 this.setLanguage(this.options.language); 371 this.context = context; 372 373 context.css('width', this.options.width).addClass('cal-context'); 374 375 this.view(); 376 return this; 377 } 378 379 Calendar.prototype.setOptions = function(object) { 380 $.extend(this.options, object); 381 if('language' in object) { 382 this.setLanguage(object.language); 383 } 384 if('modal' in object) { 385 this._update_modal(); 386 } 387 } 388 389 Calendar.prototype.setLanguage = function(lang) { 390 if(window.calendar_languages && (lang in window.calendar_languages)
390) { 391 this.locale = $.extend(true, {}, strings, calendar_languages[lang]); 392 this.options.language = lang; 393 } else { 394 this.locale = strings; 395 delete this.options.language; 396 } 397 } 398 399 Calendar.prototype._render = function() { 400 this.context.html(''); 401 this._loadTemplate(this.options.view); 402 this.stop_cycling = false; 403 404 var data = {}; 405 data.cal = this; 406 data.day = 1; 407 408 // Getting list of days in a week in correct order. Works for month and week views 409 if(getExtentedOption(this, 'first_day') == 1) { 410 data.months = [this.locale.d1, this.locale.d2, this.locale.d3, this.locale.d4, this.locale.d5, this.locale.d6, this.locale.d0] 411 } else { 412 data.months = [this.locale.d0, this.locale.d1, this.locale.d2, this.locale.d3, this.locale.d4, this.locale.d5, this.locale.d6] 413 } 414 415 // Get all events between start and end 416 var start = parseInt(this.options.position.start.getTime()); 417 var end = parseInt(this.options.position.end.getTime()); 418 419 data.events = this.getEventsBetween(start, end); 420 421 switch(this.options.view) { 422 case 'month': 423 break; 424 case 'week': 425 this._calculate_hour_minutes(data); 426 break; 427 case 'day': 428 this._calculate_hour_minutes(data); 429 break; 430 } 431 432 data.start = new Date(this.options.position.start.getTime()); 433 data.lang = this.locale; 434 435 this.context.append(this.options.templates[this.options.view](data)); 436 this._update(); 437 }; 438 439 Calendar.prototype._calculate_hour_minutes = function(data) { 440 var $self = this; 441 data.in_hour = 60 / parseInt(this.options.time_split); 442 data.hour_split = parseInt(this.options.time_split); 443 444 if(!/^\d+$/.exec(data.in_hour) || this.options.time_split > 30) { 445 $.error(this.locale.error_timedevide); 446 } 447 448 var time_start = this.options.time_start.split(":"); 449 var time_end = this.options.time_end.split(":"); 450 451 data.hours = (parseInt(time_end[0]) - parseInt(time_start[0])); 452 var lines = data.hours * data.in_hour; 453 var ms_per_line = (60000 * parseInt(this.options.time_split)); 454 455 var start = new Date(this.options.position.start.getTime()); 456 start.setHours(time_start[0]); 457 start.setMinutes(time_start[1]); 458 var end = new Date(this.options.position.start.getTime()); 459 end.setHours(time_end[0]); 460 end.setMinutes(time_end[1]); 461 462 data.all_day = []; 463 data.by_hour = []; 464 data.after_time = []; 465 data.before_time = []; 466 $.each(data.events, function(k, e) { 467 var s = new Date(parseInt(e.start)); 468 var f = new Date(parseInt(e.end)); 469 470 e.start_hour = s.getHours().toString().formatNum(2) + ':' + s.getMinutes().toString().formatNum(2); 471 e.end_hour = f.getHours().toString().formatNum(2) + ':' + f.getMinutes().toString().formatNum(2); 472 473 if(e.start < start.getTime()) { 474 warn(1); 475 e.start_hour = s.getDate() + ' ' + $self.locale['ms' + s.getMonth()] + ' ' + e.start_hour; 476 } 477 478 if(e.end > end.getTime()) { 479 warn(1); 480 e.end_hour = f.getDate() + ' ' + $self.locale['ms' + f.getMonth()] + ' ' + e.end_hour; 481 } 482 483 if(e.start < start.getTime() && e.end > end.getTime()) { 484 data.all_day.push(e); 485 return; 486 } 487 488 if(e.end < start.getTime()) { 489 data.before_time.push(e); 490 return; 491 } 492 493 if(e.start > end.getTime()) { 494 data.after_time.push(e); 495 return; 496 } 497 498 var event_start = start.getTime() - e.start; 499 500 if(event_start >= 0) { 501 e.top = 0; 502 } else { 503 e.top = Math.abs(event_start) / ms_per_line; 504 } 505 506 var lines_left = lines - e.top; 507 var lines_in_event = (e.end - e.start) / ms_per_line; 508 if(event_start >= 0) { 509 lines_in_event = (e.end - start.getTime()) / ms_per_line; 510 } 511 512 513 e.lines = lines_in_event; 514 if(lines_in_event > lines_left) { 515 e.lines = lines_left; 516 } 517 518 data.by_hour.push(e); 519 }); 520 521 //var d = new Date('2013-03-14 13:20:00'); 522 //warn(d.getTime()); 523 }; 524 525 Calendar.prototype._hour = function(hour, part) { 526 var time_start = this.options.time_start.split(":"); 527 528 var hour = "" + (parseInt(time_start[0]) + hour); 529 var minute = "" + (this.options.time_split * part); 530
531 return hour.formatNum(2) + ":" + minute.formatNum(2); 532 } 533 534 Calendar.prototype._week = function(event) { 535 this._loadTemplate('week-days'); 536 537 var t = {}; 538 var start = parseInt(this.options.position.start.getTime()); 539 var end = parseInt(this.options.position.end.getTime()); 540 var events = []; 541 var self = this; 542 var first_day = getExtentedOption(this, 'first_day'); 543 544 $.each(this.getEventsBetween(start, end), function(k, event) { 545 event.start_day = new Date(parseInt(event.start)).getDay(); 546 if(first_day == 1) { 547 event.start_day = (event.start_day + 6) % 7; 548 } 549 if((event.end - event.start) <= 86400000) { 550 event.days = 1; 551 } else { 552 event.days = ((event.end - event.start) / 86400000); 553 } 554 555 if(event.start < start) { 556 557 event.days = event.days - ((start - event.start) / 86400000); 558 event.start_day = 0; 559 } 560 561 event.days = Math.ceil(event.days); 562 563 if(event.start_day + event.days > 7) { 564 event.days = 7 - (event.start_day); 565 } 566 567 events.push(event); 568 }); 569 t.events = events; 570 t.cal = this; 571 return self.options.templates['week-days'](t); 572 } 573 574 Calendar.prototype._month = function(month) { 575 this._loadTemplate('year-month'); 576 577 var t = {cal: this}; 578 var newmonth = month + 1; 579 t.data_day = this.options.position.start.getFullYear() + '-' + (newmonth < 10 ? '0' + newmonth : newmonth) + '-' + '01'; 580 t.month_name = this.locale['m' + month]; 581 582 var curdate = new Date(this.options.position.start.getFullYear(), month, 1, 0, 0, 0); 583 t.start = parseInt(curdate.getTime()); 584 t.end = parseInt(new Date(this.options.position.start.getFullYear(), month + 1, 1, 0, 0, 0).getTime()); 585 t.events = this.getEventsBetween(t.start, t.end); 586 return this.options.templates['year-month'](t); 587 } 588 589 Calendar.prototype._day = function(week, day) { 590 this._loadTemplate('month-day'); 591 592 var t = {tooltip: '', cal: this}; 593 var cls = this.options.classes.months.outmonth; 594 595 var firstday = this.options.position.start.getDay(); 596 if(getExtentedOption(this, 'first_day') == 2) { 597 firstday++; 598 } else { 599 firstday = (firstday == 0 ? 7 : firstday); 600 } 601 602 day = (day - firstday) + 1; 603 var curdate = new Date(this.options.position.start.getFullYear(), this.options.position.start.getMonth(), day, 0, 0, 0); 604 605 // if day of the current month 606 if(day > 0) { 607 cls = this.options.classes.months.inmonth; 608 } 609 // stop cycling table rows; 610 var daysinmonth = (new Date(this.options.position.end.getTime() - 1)).getDate(); 611 if((day + 1) > daysinmonth) { 612 this.stop_cycling = true; 613 } 614 // if day of the next month 615 if(day > daysinmonth) { 616 day = day - daysinmonth; 617 cls = this.options.classes.months.outmonth; 618 } 619 620 cls = $.trim(cls + " " + this._getDayClass("months", curdate)); 621 622 if(day <= 0) { 623 var daysinprevmonth = (new Date(this.options.position.start.getFullYear(), this.options.position.start.getMonth(), 0)).getDate(); 624 day = daysinprevmonth - Math.abs(day); 625 cls += ' cal-month-first-row'; 626 } 627 628 var holiday = this._getHoliday(curdate); 629 if(holiday !== false) { 630 t.tooltip = holiday; 631 } 632 633 t.data_day = curdate.getFullYear() + '-' + curdate.getMonthFormatted() + '-' + (day < 10 ? '0' + day : day); 634 t.cls = cls; 635 t.day = day; 636 637 t.start = parseInt(curdate.getTime()); 638 t.end = parseInt(t.start + 86400000); 639 t.events = this.getEventsBetween(t.start, t.end); 640 return this.options.templates['month-day'](t); 641 } 642 643 Calendar.prototype._getHoliday = function(date) { 644 var result = false; 645 $.each(getHolidays(this, date.getFullYear()), function() { 646 var found = false; 647 $.each(this.days, function() { 648 if(this.toDateString() == date.toDateString()) { 649 found = true; 650 return false; 651 } 652 }); 653 if(found) { 654 result = this.name; 655 return false; 656 } 657 }); 658 return result; 659 }; 660 661 Calendar.prototype._getHolidayName = function(date) { 662 var holiday = this._getHoliday(date); 663 return (holiday === false) ? "" : holiday; 664 }; 665 666 Calendar.prototype._getDayClass = function(class_group, date) { 667 var self = this; 668 var addClass = function(which, to) { 669 var cls; 670 cls = (self.options.classes && (class_group in self.options.classes) && (which in self.options.classes[class_group])) ? self.options.classes[class_group][which] : ""; 671 if((typeof(cls) == "string") && cls.length) { 672 to.push(cls); 673 } 674 }; 675 var classes = []; 676 if(date.toDateString() == (new Date()).toDateString()) { 677 addClass("today", classes); 678 } 679 var holiday = this._getHoliday(date); 680 if(holiday !== false) { 681 addClass("holidays", classes); 682 } 683 switch(date.getDay()) { 684 case 0: 685 addClass("sunday", classes); 686 break; 687 case 6: 688 addClass("saturday", classes); 689 break; 690 } 691 692 addClass(date.toDateString(), classes); 693 694 return classes.join(" "); 695 }; 696 697 Calendar.prototype.view = function(view) { 698 if(view) { 699 if(!this.options.views[view].enable) { 700 return; 701 } 702 this.options.view = view; 703 } 704 705 706 this._init_position(); 707 this._loadEvents(); 708 this._render(); 709
710 this.options.onAfterViewLoad.call(this, this.options.view); 711 }; 712 713 Calendar.prototype.navigate = function(where, next) { 714 var to = $.extend({}, this.options.position); 715 if(where == 'next') { 716 switch(this.options.view) { 717 case 'year': 718 to.start.setFullYear(this.options.position.start.getFullYear() + 1); 719 break; 720 case 'month': 721 to.start.setMonth(this.options.position.start.getMonth() + 1); 722 break; 723 case 'week': 724 to.start.setDate(this.options.position.start.getDate() + 7); 725 break; 726 case 'day': 727 to.start.setDate(this.options.position.start.getDate() + 1); 728 break; 729 } 730 } else if(where == 'prev') { 731 switch(this.options.view) { 732 case 'year': 733 to.start.setFullYear(this.options.position.start.getFullYear() - 1); 734 break; 735 case 'month': 736 to.start.setMonth(this.options.position.start.getMonth() - 1); 737 break; 738 case 'week': 739 to.start.setDate(this.options.position.start.getDate() - 7); 740 break; 741 case 'day': 742 to.start.setDate(this.options.position.start.getDate() - 1); 743 break; 744 } 745 } else if(where == 'today') { 746 to.start.setTime(new Date().getTime()); 747 } 748 else { 749 $.error(this.locale.error_where.format(where)) 750 } 751 this.options.day = to.start.getFullYear() + '-' + to.start.getMonthFormatted() + '-' + to.start.getDateFormatted(); 752 this.view(); 753 if(_.isFunction(next)) { 754 next(); 755 } 756 }; 757 758 Calendar.prototype._init_position = function() { 759 var year, month, day; 760 761 if(this.options.day == 'now') { 762 var date = new Date(); 763 year = date.getFullYear(); 764 month = date.getMonth(); 765 day = date.getDate(); 766 } else if(this.options.day.match(/^\d{4}-\d{2}-\d{2}$/g)) { 767 var list = this.options.day.split('-'); 768 year = parseInt(list[0], 10); 769 month = parseInt(list[1], 10) - 1; 770 day = parseInt(list[2], 10); 771 } 772 else { 773 $.error(this.locale.error_dateformat.format(this.options.day)); 774 } 775 776 switch(this.options.view) { 777 case 'year': 778 this.options.position.start.setTime(new Date(year, 0, 1).getTime()); 779 this.options.position.end.setTime(new Date(year + 1, 0, 1).getTime()); 780 break; 781 case 'month': 782 this.options.position.start.setTime(new Date(year, month, 1).getTime()); 783 this.options.position.end.setTime(new Date(year, month + 1, 1).getTime()); 784 break; 785 case 'day': 786 this.options.position.start.setTime(new Date(year, month, day).getTime()); 787 this.options.position.end.setTime(new Date(year, month, day + 1).getTime()); 788 break; 789 case 'week': 790 var curr = new Date(year, month, day); 791 var first; 792 if(getExtentedOption(this, 'first_day') == 1) { 793 first = curr.getDate() - ((curr.getDay() + 6) % 7); 794 } 795 else { 796 first = curr.getDate() - curr.getDay(); 797 } 798 this.options.position.start.setTime(new Date(year, month, first).getTime()); 799 this.options.position.end.setTime(new Date(year, month, first + 7).getTime()); 800 break; 801 default: 802 $.error(this.locale.error_noview.format(this.options.view)) 803 } 804 return this; 805 }; 806 807 Calendar.prototype.getTitle = function() { 808 var p = this.options.position.start; 809 switch(this.options.view) { 810 case 'year': 811 return this.locale.title_year.format(p.getFullYear()); 812 break; 813 case 'month': 814 return this.locale.title_month.format(this.locale['m' + p.getMonth()], p.getFullYear()); 815 break; 816 case 'week': 817 return this.locale.title_week.format(p.getWeek(), p.getFullYear()); 818 break; 819 case 'day': 820 return this.locale.title_day.format(this.locale['d' + p.getDay()], p.getDate(), this.locale['m' + p.getMonth()], p.getFullYear()); 821 break; 822 } 823 return; 824 }; 825 826 Calendar.prototype.isToday = function() { 827 var now = new Date().getTime(); 828 829 return ((now > this.options.position.start) && (now < this.options.position.end)); 830 } 831 832 Calendar.prototype.getStartDate = function() { 833 return this.options.position.start; 834 } 835 836 Calendar.prototype.getEndDate = function() { 837 return this.options.position.end; 838 } 839 840 Calendar.prototype._loadEvents = function() { 841 var self = this; 842 var source = null; 843 if('events_source' in this.options && this.options.events_source !== '') { 844 source = this.options.events_source; 845 } 846 else if('events_url' in this.options) { 847 source = this.options.events_url; 848 warn('The events_url option is DEPRECATED and it will be REMOVED in near future. Please use events_source instead.'); 849 } 850 var loader; 851 switch($.type(source)) { 852 case 'function': 853 loader = function() { 854 return source(self.options.position.start, self.options.position.end, browser_timezone); 855 }; 856 break; 857 case 'array': 858 loader = function() { 859 return [].concat(source); 860 }; 861 break; 862 case 'string': 863 if(source.length) { 864 loader = function() { 865 var events = []; 866 var params = {from: self.options.position.start.getTime(), to: self.options.position.end.getTime()};
867 if(browser_timezone.length) { 868 params.browser_timezone = browser_timezone; 869 } 870 $.ajax({ 871 url: buildEventsUrl(source, params), 872 dataType: 'json', 873 type: 'GET', 874 async: false 875 }).done(function(json) { 876 if(!json.success) { 877 $.error(json.error); 878 } 879 if(json.result) { 880 events = json.result; 881 } 882 }); 883 return events; 884 }; 885 } 886 break; 887 } 888 if(!loader) { 889 $.error(this.locale.error_loadurl); 890 } 891 this.options.onBeforeEventsLoad.call(this, function() { 892 self.options.events = loader(); 893 self.options.events.sort(function(a, b) { 894 var delta; 895 delta = a.start - b.start; 896 if(delta == 0) { 897 delta = a.end - b.end; 898 } 899 return delta; 900 }); 901 self.options.onAfterEventsLoad.call(self, self.options.events); 902 }); 903 }; 904 905 Calendar.prototype._templatePath = function(name) { 906 if(typeof this.options.tmpl_path == 'function') { 907 return this.options.tmpl_path(name) 908 } 909 else { 910 return this.options.tmpl_path + name + '.html'; 911 } 912 }; 913 914 Calendar.prototype._loadTemplate = function(name) { 915 if(this.options.templates[name]) { 916 return; 917 } 918 var self = this; 919 $.ajax({ 920 url: self._templatePath(name), 921 dataType: 'html', 922 type: 'GET', 923 async: false, 924 cache: this.options.tmpl_cache 925 }).done(function(html) { 926 self.options.templates[name] = _.template(html); 927 }); 928 }; 929 930 Calendar.prototype._update = function() { 931 var self = this; 932 933 $('*[data-toggle="tooltip"]').tooltip({container: 'body'}); 934 935 /*$('*[data-cal-date]').click(function() { 936 var view = $(this).data('cal-view'); 937 self.options.day = $(this).data('cal-date'); 938 self.view(view); 939 }); 940 $('.cal-cell').dblclick(function() { 941 var view = $('[data-cal-date]', this).data('cal-view'); 942 self.options.day = $('[data-cal-date]', this).data('cal-date'); 943 self.view(view); 944 });*/ 945 946 this['_update_' + this.options.view](); 947 948 this._update_modal(); 949 950 }; 951 952 Calendar.prototype._update_modal = function() { 953 var self = this; 954 955 $('a[data-event-id]', this.context).unbind('click'); 956 957 if(!self.options.modal) { 958 return; 959 } 960 961 var modal = $(self.options.modal); 962 963 if(!modal.length) { 964 return; 965 } 966 967 var ifrm = null; 968 if(self.options.modal_type == "iframe") { 969 ifrm = $(document.createElement("iframe")) 970 .attr({ 971 width: "100%", 972 frameborder: "0" 973 }); 974 } 975 976 977 $('a[data-event-id]', this.context).on('click', function(event) { 978 event.preventDefault(); 979 event.stopPropagation(); 980 981 var url = $(this).attr('href'); 982 var id = $(this).data("event-id"); 983 var event = _.find(self.options.events, function(event) { 984 return event.id == id 985 }); 986 987 if(self.options.modal_type == "iframe") { 988 ifrm.attr('src', url); 989 $('.modal-body', modal).html(ifrm); 990 } 991 992 if(!modal.data('handled.bootstrap-calendar') || (modal.data('handled.bootstrap-calendar') && modal.data('handled.event-id') != event.id)) { 993 modal 994 .on('show.bs.modal', function() { 995 var modal_body = $(this).find('.modal-body'); 996 switch(self.options.modal_type) { 997 case "iframe" : 998 var height = modal_body.height() - parseInt(modal_body.css('padding-top'), 10) - parseInt(modal_body.css('padding-bottom'), 10); 999 $(this).find('iframe').height(Math.max(height, 50)); 1000 break; 1001 1002 case "ajax": 1003 $.ajax({url: url, dataType: "html", async: false, success: function(data) { 1004 modal_body.html(data); 1005 }}); 1006 break; 1007 1008 case "template": 1009 self._loadTemplate("modal"); 1010 // also serve calendar instance to underscore template to be able to access current language strings 1011 modal_body.html(self.options.templates["modal"]({"event": event, "calendar": self})) 1012 break; 1013 } 1014 1015 // set the title of the bootstrap modal 1016 if(_.isFunction(self.options.modal_title)) { 1017 modal.find("h3").html(self.options.modal_title(event)); 1018 } 1019 }) 1020 .on('shown.bs.modal', function() { 1021 self.options.onAfterModalShown.call(self, self.options.events); 1022 }) 1023 .on('hidden.bs.modal', function() { 1024 self.options.onAfterModalHidden.call(self, self.options.events); 1025 }) 1026 .data('handled.bootstrap-calendar', true).data('handled.event-id', event.id); 1027 } 1028 modal.modal('show'); 1029 }); 1030 }; 1031 1032 Calendar.prototype._update_day = function() { 1033 $('#cal-day-panel').height($('#cal-day-panel-hour').height()); 1034 }; 1035 1036 Calendar.prototype._update_week = function() { 1037 }; 1038 1039 Calendar.prototype._update_year = function() { 1040 this._update_month_year(); 1041 }; 1042
1043 Calendar.prototype._update_month = function() { 1044 this._update_month_year(); 1045 1046 var self = this; 1047 1048 var week = $(document.createElement('div')).attr('id', 'cal-week-box'); 1049 var start = this.options.position.start.getFullYear() + '-' + this.options.position.start.getMonthFormatted() + '-'; 1050 $('.cal-month-box .cal-row-fluid') 1051 .on('mouseenter', function() { 1052 var p = new Date(self.options.position.start); 1053 var child = $('.cal-cell1:first-child .cal-month-day', this); 1054 var day = (child.hasClass('cal-month-first-row') ? 1 : $('[data-cal-date]', child).text()); 1055 p.setDate(parseInt(day)); 1056 day = (day < 10 ? '0' + day : day); 1057 week.html(self.locale.week.format(p.getWeek())); 1058 week.attr('data-cal-week', start + day).show().appendTo(child); 1059 }) 1060 .on('mouseleave', function() { 1061 week.hide(); 1062 }) 1063 ; 1064 1065 week.click(function() { 1066 self.options.day = $(this).data('cal-week'); 1067 self.view('week'); 1068 }); 1069 1070 $('a.event').mouseenter(function() { 1071 $('a[data-event-id="' + $(this).data('event-id') + '"]').closest('.cal-cell1').addClass('day-highlight dh-' + $(this).data('event-class')); 1072 }); 1073 $('a.event').mouseleave(function() { 1074 $('div.cal-cell1').removeClass('day-highlight dh-' + $(this).data('event-class')); 1075 }); 1076 }; 1077 1078 Calendar.prototype._update_month_year = function() { 1079 if(!this.options.views[this.options.view].slide_events) { 1080 return; 1081 } 1082 var self = this; 1083 var activecell = 0; 1084 var downbox = $(document.createElement('div')).attr('id', 'cal-day-tick').html('<i class="icon-chevron-down glyphicon glyphicon-chevron-down"></i>'); 1085 1086 $('.cal-month-day, .cal-year-box .span3') 1087 .on('mouseenter', function() { 1088 if($('.events-list', this).length == 0) return; 1089 if($(this).children('[data-cal-date]').text() == self.activecell) return; 1090 downbox.show().appendTo(this); 1091 }) 1092 .on('mouseleave', function() { 1093 downbox.hide(); 1094 }) 1095 .on('click', function(event) { 1096 if($('.events-list', this).length == 0) return; 1097 if($(this).children('[data-cal-date]').text() == self.activecell) return; 1098 showEventsList(event, downbox, slider, self); 1099 }) 1100 ; 1101 1102 var slider = $(document.createElement('div')).attr('id', 'cal-slide-box'); 1103 slider.hide().click(function(event) { 1104 event.stopPropagation(); 1105 }); 1106 1107 this._loadTemplate('events-list'); 1108 1109 downbox.click(function(event) { 1110 showEventsList(event, $(this), slider, self); 1111 }); 1112 }; 1113 1114 Calendar.prototype.getEventsBetween = function(start, end) { 1115 var events = []; 1116 $.each(this.options.events, function() { 1117 if(this.start == null) { 1118 return true; 1119 } 1120 var event_end = this.end || this.start; 1121 if((parseInt(this.start) < end) && (parseInt(event_end) >= start)) { 1122 events.push(this); 1123 } 1124 }); 1125 return events; 1126 }; 1127 1128 function showEventsList(event, that, slider, self) { 1129 1130 event.stopPropagation(); 1131 1132 var that = $(that); 1133 var cell = that.closest('.cal-cell'); 1134 var row = cell.closest('.cal-before-eventlist'); 1135 var tick_position = cell.data('cal-row'); 1136 1137 that.fadeOut('fast'); 1138 1139 slider.slideUp('fast', function() { 1140 var event_list = $('.events-list', cell); 1141 slider.html(self.options.templates['events-list']({ 1142 cal: self, 1143 events: self.getEventsBetween(parseInt(event_list.data('cal-start')), parseInt(event_list.data('cal-end'))) 1144 })); 1145 row.after(slider); 1146 self.activecell = $('[data-cal-date]', cell).text(); 1147 $('#cal-slide-tick').addClass('tick' + tick_position).show(); 1148 slider.slideDown('fast', function() { 1149 $('body').one('click', function() { 1150 slider.slideUp('fast'); 1151 self.activecell = 0; 1152 }); 1153 }); 1154 }); 1155 1156 $('a.event-item').mouseenter(function() { 1157 $('a[data-event-id="' + $(this).data('event-id') + '"]').closest('.cal-cell1').addClass('day-highlight dh-' + $(this).data('event-class')); 1158 }); 1159 $('a.event-item').mouseleave(function() { 1160 $('div.cal-cell1').removeClass('day-highlight dh-' + $(this).data('event-class')); 1161 }); 1162 1163 // Wait 400ms before updating the modal (400ms is the time for the slider to fade out and slide up) 1164 setTimeout(function() { 1165 self._update_modal(); 1166 }, 400); 1167 } 1168 1169 function getEasterDate(year, offsetDays) { 1170 var a = year % 19; 1171 var b = Math.floor(year / 100); 1172 var c = year % 100; 1173 var d = Math.floor(b / 4); 1174 var e = b % 4; 1175 var f = Math.floor((b + 8) / 25); 1176 var g = Math.floor((b - f + 1) / 3);
1177 var h = (19 * a + b - d - g + 15) % 30; 1178 var i = Math.floor(c / 4); 1179 var k = c % 4; 1180 var l = (32 + 2 * e + 2 * i - h - k) % 7; 1181 var m = Math.floor((a + 11 * h + 22 * l) / 451); 1182 var n0 = (h + l + 7 * m + 114) 1183 var n = Math.floor(n0 / 31) - 1; 1184 var p = n0 % 31 + 1; 1185 return new Date(year, n, p + (offsetDays ? offsetDays : 0), 0, 0, 0); 1186 } 1187 1188 $.fn.calendar = function(params) { 1189 return new Calendar(params, this); 1190 } 1191 1192 1193 1194}(jQuery));
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.