4 * based on jquery fullcalendar
8 Roo.calendar = Roo.calendar || {};
12 * @class Roo.calendar.View
13 * @extends Roo.bootstrap.Component
14 * Bootstrap Calendar class
17 * Create a new Container
18 * @param {Object} config The config object
24 Roo.calendar.View = function(config){
25 Roo.calendar.View.superclass.constructor.call(this, config);
29 * Fires when a date is selected
30 * @param {DatePicker} this
31 * @param {Date} date The selected date
36 * Fires when the displayed month changes
37 * @param {DatePicker} this
38 * @param {Date} date The selected month
43 * Fires when mouse over an event
44 * @param {Calendar} this
45 * @param {event} Event
50 * Fires when the mouse leaves an
51 * @param {Calendar} this
57 * Fires when the mouse click an
58 * @param {Calendar} this
67 Roo.extend(Roo.calendar.View, Roo.BoxComponent, {
70 * @cfg {Number} startDay
71 * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
76 skipNavHeader : false,
77 skipMonthHeader : false,
82 navHeader: function() {
83 // header is a bit specific to bootstrap implementation - eg. roo would not use this..
85 var fc_button = function(name, corner, style, content ) {
88 cls : 'fc-button fc-button-'+name+' fc-state-default ' +
90 'fc-corner-' + corner.split(' ').join(' fc-corner-') :
93 html : '<SPAN class="fc-text-'+style+ '">'+content +'</SPAN>',
101 style : 'width:100%',
108 cls : 'fc-header-left',
110 fc_button('prev', 'left', 'arrow', '‹' ),
111 fc_button('next', 'right', 'arrow', '›' ),
112 { tag: 'span', cls: 'fc-header-space' },
113 fc_button('today', 'left right', '', 'today' ) // neds state disabled..
121 cls : 'fc-header-center',
125 cls: 'fc-header-title',
128 html : 'month / year'
136 cls : 'fc-header-right',
138 // fc_button('month', 'left', '', 'month' ),
139 // fc_button('week', '', '', 'week' ),
140 // fc_button('day', 'right', '', 'day' )
152 monthHeader : function ()
155 // fixme - handle this.
157 for (var i =0; i < Date.dayNames.length; i++) {
158 var d = Date.dayNames[i];
161 cls : 'fc-day-header fc-' + d.substring(0,3).toLowerCase() + ' fc-widget-header',
162 html : d.substring(0,3)
166 ret[0].cls += ' fc-first';
167 ret[6].cls += ' fc-last';
171 monthBody : function()
173 var cal_cell = function(n) {
176 cls : 'fc-day fc-'+n + ' fc-widget-content', ///fc-other-month fc-past
181 cls: 'fc-day-number',
185 cls: 'fc-day-content',
189 style: 'position: relative;' // height: 17px;
203 for (var r = 0; r < 6; r++) {
210 for (var i =0; i < Date.dayNames.length; i++) {
211 var d = Date.dayNames[i];
212 row.cn.push(cal_cell(d.substring(0,3).toLowerCase()));
215 row.cn[0].cls+=' fc-first';
216 row.cn[0].cn[0].style = 'min-height:90px';
217 row.cn[6].cls+=' fc-last';
221 ret[0].cls += ' fc-first';
222 ret[4].cls += ' fc-prev-last';
223 ret[5].cls += ' fc-last';
228 monthTable : function()
232 cls: 'fc-border-separate',
233 style : 'width:100%',
237 if (!this.skipMonthHeader) {
243 cls : 'fc-first fc-last',
244 cn : this.monthHeader()
251 cn : this.monthBody()
259 getAutoCreate : function(){
263 if (!this.skipNavHeader) {
264 mbody.push(this.navHeader());
268 style : "position: relative;",
271 cls : 'fc-view fc-view-month fc-grid',
272 style : 'position: relative',
276 cls : 'fc-event-container',
277 style : 'position:absolute;z-index:8;top:0;left:0;'
295 initEvents : function()
298 throw "can not find store for calendar";
301 this.store = Roo.factory(this.store, Roo.data);
302 this.store.on('load', this.onLoad, this);
306 this.cells = this.el.select('.fc-day',true);
307 //Roo.log(this.cells);
308 this.textNodes = this.el.query('.fc-day-number');
309 this.cells.addClassOnOver('fc-state-hover');
312 if (!this.skipNavHeader) {
314 this.el.select('.fc-button-prev',true).on('click', this.showPrevMonth, this);
315 this.el.select('.fc-button-next',true).on('click', this.showNextMonth, this);
316 this.el.select('.fc-button-today',true).on('click', this.showToday, this);
317 this.el.select('.fc-button',true).addClassOnOver('fc-state-hover');
319 this.on('monthchange', this.onMonthChange, this);
321 this.update(new Date().clearTime());
324 resize : function() {
325 var sz = this.el.getSize();
327 this.el.select('.fc-day-header',true).setWidth(sz.width / 7);
328 this.el.select('.fc-day-content div',true).setHeight(34);
333 showPrevMonth : function(e){
334 this.update(this.activeDate.add("mo", -1));
336 showToday : function(e){
337 this.update(new Date().clearTime());
340 showNextMonth : function(e){
341 this.update(this.activeDate.add("mo", 1));
345 showPrevYear : function(){
346 this.update(this.activeDate.add("y", -1));
350 showNextYear : function(){
351 this.update(this.activeDate.add("y", 1));
356 update : function(date)
358 var vd = this.activeDate;
359 this.activeDate = date;
360 // if(vd && this.el){
361 // var t = date.getTime();
362 // if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
363 // Roo.log('using add remove');
365 // this.fireEvent('monthchange', this, date);
367 // this.cells.removeClass("fc-state-highlight");
368 // this.cells.each(function(c){
369 // if(c.dateValue == t){
370 // c.addClass("fc-state-highlight");
371 // setTimeout(function(){
372 // try{c.dom.firstChild.focus();}catch(e){}
382 var days = date.getDaysInMonth();
384 var firstOfMonth = date.getFirstDateOfMonth();
385 var startingPos = firstOfMonth.getDay()-this.startDay;
387 if(startingPos < this.startDay){
391 var pm = date.add(Date.MONTH, -1);
392 var prevStart = pm.getDaysInMonth()-startingPos;
394 this.cells = this.el.select('.fc-day',true);
395 this.textNodes = this.el.query('.fc-day-number');
396 this.cells.addClassOnOver('fc-state-hover');
398 var cells = this.cells.elements;
399 var textEls = this.textNodes;
401 Roo.each(cells, function(cell){
402 cell.removeClass([ 'fc-past', 'fc-other-month', 'fc-future', 'fc-state-highlight', 'fc-state-disabled']);
407 // convert everything to numbers so it's fast
409 var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
412 //Roo.log(prevStart);
414 var today = new Date().clearTime().getTime();
415 var sel = date.clearTime().getTime();
416 var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
417 var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
418 var ddMatch = this.disabledDatesRE;
419 var ddText = this.disabledDatesText;
420 var ddays = this.disabledDays ? this.disabledDays.join("") : false;
421 var ddaysText = this.disabledDaysText;
422 var format = this.format;
424 var setCellClass = function(cal, cell){
426 //Roo.log('set Cell Class');
434 cell.className += " fc-cell-" + t;
437 cell.className += " fc-today";
438 cell.className += " fc-state-highlight";
439 cell.title = cal.todayText;
442 // disable highlight in other month..
443 //cell.className += " fc-state-highlight";
448 cell.className = " fc-state-disabled";
449 cell.title = cal.minText;
453 cell.className = " fc-state-disabled";
454 cell.title = cal.maxText;
458 if(ddays.indexOf(d.getDay()) != -1){
459 cell.title = ddaysText;
460 cell.className = " fc-state-disabled";
463 if(ddMatch && format){
464 var fvalue = d.dateFormat(format);
465 if(ddMatch.test(fvalue)){
466 cell.title = ddText.replace("%0", fvalue);
467 cell.className = " fc-state-disabled";
471 if (!cell.initialClassName) {
472 cell.initialClassName = cell.dom.className;
475 cell.dom.className = cell.initialClassName + ' ' + cell.className;
480 for(; i < startingPos; i++) {
481 textEls[i].innerHTML = (++prevStart);
482 d.setDate(d.getDate()+1);
484 cells[i].className = "fc-past fc-other-month";
485 setCellClass(this, cells[i]);
490 for(; i < days; i++){
491 intDay = i - startingPos + 1;
492 textEls[i].innerHTML = (intDay);
493 d.setDate(d.getDate()+1);
495 cells[i].className = ''; // "x-date-active";
496 setCellClass(this, cells[i]);
501 textEls[i].innerHTML = (++extraDays);
502 d.setDate(d.getDate()+1);
504 cells[i].className = "fc-future fc-other-month";
505 setCellClass(this, cells[i]);
508 this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
510 var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
512 this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
513 this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
516 this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
517 this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
520 this.fireEvent('monthchange', this, date);
524 if(!this.internalRender){
525 var main = this.el.dom.firstChild;
526 var w = main.offsetWidth;
527 this.el.setWidth(w + this.el.getBorderWidth("lr"));
528 Roo.fly(main).setWidth(w);
529 this.internalRender = true;
530 // opera does not respect the auto grow header center column
531 // then, after it gets a width opera refuses to recalculate
532 // without a second pass
533 if(Roo.isOpera && !this.secondPass){
534 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
535 this.secondPass = true;
536 this.update.defer(10, this, [date]);
543 findCell : function(dt) {
544 dt = dt.clearTime().getTime();
546 this.cells.each(function(c){
547 //Roo.log("check " +c.dateValue + '?=' + dt);
548 if(c.dateValue == dt){
558 findCells : function(ev) {
559 var s = ev.start_dt.clone().clearTime().getTime();
561 var e= ev.end_dt.clone().clearTime().getTime();
564 this.cells.each(function(c){
565 ////Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
579 findBestRow: function(cells)
583 for (var i =0 ; i < cells.length;i++) {
584 ret = Math.max(cells[i].rows || 0,ret);
591 addItem : function(ev)
593 // look for vertical location slot in
594 var cells = this.findCells(ev);
596 ev.row = this.findBestRow(cells);
598 // work out the location.
602 for(var i =0; i < cells.length; i++) {
610 if (crow.start.getY() == cells[i].getY()) {
628 for (var i = 0; i < cells.length;i++) {
629 cells[i].rows = Math.max(cells[i].rows || 0 , ev.row + 1 );
633 this.calevents.push(ev);
636 clearEvents: function() {
642 Roo.each(this.cells.elements, function(c){
646 Roo.each(this.calevents, function(e) {
647 Roo.each(e.els, function(el) {
648 el.un('mouseenter' ,this.onEventEnter, this);
649 el.un('mouseleave' ,this.onEventLeave, this);
656 renderEvents: function()
658 // first make sure there is enough space..
660 this.cells.each(function(c) {
661 // c.select('.fc-day-content div',true).first().setHeight(20);
662 c.select('.fc-day-content div',true).first().setHeight(Math.max(34, c.rows * 20));
665 for (var e = 0; e < this.calevents.length; e++) {
666 var ev = this.calevents[e];
667 var cells = ev.cells;
670 for(var i =0; i < rows.length; i++) {
672 var startCell = Roo.select('.fc-cell-' + rows[i].start.dateValue + ' .fc-day-content div', true).first();
674 // how many rows should it span..
677 cls : 'roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable',
678 style : 'position: absolute', // left: 387px; width: 121px; top: 359px;
683 cls: 'fc-event-inner',
687 cls: 'fc-event-time',
688 html : cells.length > 1 ? '' : ev.start_dt.format('h:ia')
692 cls: 'fc-event-title',
693 html : String.format('{0}', ev.title)
700 cls: 'ui-resizable-handle ui-resizable-e',
701 html : '  '
707 cfg.cls += ' fc-event-start';
710 if ((i+1) == rows.length) {
711 cfg.cls += ' fc-event-end';
714 // var ctr = this.el.select('.fc-event-container',true).first();
715 var cg = startCell.createChild(cfg);
717 cg.on('mouseenter' ,this.onEventEnter, this, ev);
718 cg.on('mouseleave' ,this.onEventLeave, this, ev);
719 cg.on('click', this.onEventClick, this, ev);
723 var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
724 var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
726 cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);
728 var boxes = Math.ceil((rows[i].end.dateValue - rows[i].start.dateValue) / 86400000) + 1;
729 // cg.setWidth(ebox.right - sbox.x -2);
731 cg.setWidth(134 * boxes);
741 onEventEnter: function (e, el,event,d) {
742 this.fireEvent('evententer', this, el, event);
745 onEventLeave: function (e, el,event,d) {
746 this.fireEvent('eventleave', this, el, event);
749 onEventClick: function (e, el,event,d) {
750 this.fireEvent('eventclick', this, el, event);
753 onMonthChange: function () {
757 onLoad: function () {
760 //Roo.log('calendar onload');
764 if(this.store.getCount() > 0){
765 this.store.data.each(function(d){
769 var add = Roo.apply({}, d.data);
770 if (typeof(add.end_dt) == 'undefined') {
771 Roo.log("Missing End time in calendar data: ");
775 if (typeof(add.start_dt) == 'undefined') {
776 Roo.log("Missing Start time in calendar data: ");
780 add.start_dt = typeof(add.start_dt) == 'string' ? Date.parseDate(add.start_dt,'Y-m-d H:i:s') : add.start_dt,
781 add.end_dt = typeof(add.end_dt) == 'string' ? Date.parseDate(add.end_dt,'Y-m-d H:i:s') : add.end_dt,
782 add.id = add.id || d.id;
783 add.title = add.title || '??';
786 // other than the 'required' coluns, we should just pass the data from the store.
791 start: new Date(d.data.start_dt),
792 end : new Date(d.data.end_dt),
793 time : d.data.start_time,
794 title : d.data.title,
795 description : d.data.description,