4 * based on jquery fullcalendar
8 Roo.bootstrap = Roo.bootstrap || {};
10 * @class Roo.bootstrap.Calendar
11 * @extends Roo.bootstrap.Component
12 * Bootstrap Calendar class
13 * @cfg {Boolean} loadMask (true|false) default false
14 * @cfg {Object} header generate the user specific header of the calendar, default false
17 * Create a new Container
18 * @param {Object} config The config object
23 Roo.bootstrap.Calendar = function(config){
24 Roo.bootstrap.Calendar.superclass.constructor.call(this, config);
28 * Fires when a date is selected
29 * @param {DatePicker} this
30 * @param {Date} date The selected date
35 * Fires when the displayed month changes
36 * @param {DatePicker} this
37 * @param {Date} date The selected month
42 * Fires when mouse over an event
43 * @param {Calendar} this
44 * @param {event} Event
49 * Fires when the mouse leaves an
50 * @param {Calendar} this
56 * Fires when the mouse click an
57 * @param {Calendar} this
66 Roo.extend(Roo.bootstrap.Calendar, Roo.bootstrap.Component, {
69 * @cfg {Number} startDay
70 * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
78 getAutoCreate : function(){
81 var fc_button = function(name, corner, style, content ) {
84 cls : 'fc-button fc-button-'+name+' fc-state-default ' +
86 'fc-corner-' + corner.split(' ').join(' fc-corner-') :
89 html : '<SPAN class="fc-text-'+style+ '">'+content +'</SPAN>',
100 style : 'width:100%',
107 cls : 'fc-header-left',
109 fc_button('prev', 'left', 'arrow', '‹' ),
110 fc_button('next', 'right', 'arrow', '›' ),
111 { tag: 'span', cls: 'fc-header-space' },
112 fc_button('today', 'left right', '', 'today' ) // neds state disabled..
120 cls : 'fc-header-center',
124 cls: 'fc-header-title',
127 html : 'month / year'
135 cls : 'fc-header-right',
137 /* fc_button('month', 'left', '', 'month' ),
138 fc_button('week', '', '', 'week' ),
139 fc_button('day', 'right', '', 'day' )
151 header = this.header;
154 var cal_heads = function() {
156 // fixme - handle this.
158 for (var i =0; i < Date.dayNames.length; i++) {
159 var d = Date.dayNames[i];
162 cls : 'fc-day-header fc-' + d.substring(0,3).toLowerCase() + ' fc-widget-header',
163 html : d.substring(0,3)
167 ret[0].cls += ' fc-first';
168 ret[6].cls += ' fc-last';
171 var cal_cell = function(n) {
174 cls : 'fc-day fc-'+n + ' fc-widget-content', ///fc-other-month fc-past
179 cls: 'fc-day-number',
183 cls: 'fc-day-content',
187 style: 'position: relative;' // height: 17px;
199 var cal_rows = function() {
202 for (var r = 0; r < 6; r++) {
209 for (var i =0; i < Date.dayNames.length; i++) {
210 var d = Date.dayNames[i];
211 row.cn.push(cal_cell(d.substring(0,3).toLowerCase()));
214 row.cn[0].cls+=' fc-first';
215 row.cn[0].cn[0].style = 'min-height:90px';
216 row.cn[6].cls+=' fc-last';
220 ret[0].cls += ' fc-first';
221 ret[4].cls += ' fc-prev-last';
222 ret[5].cls += ' fc-last';
229 cls: 'fc-border-separate',
230 style : 'width:100%',
238 cls : 'fc-first fc-last',
257 style : "position: relative;",
260 cls : 'fc-view fc-view-month fc-grid',
261 style : 'position: relative',
265 cls : 'fc-event-container',
266 style : 'position:absolute;z-index:8;top:0;left:0;'
284 initEvents : function()
287 throw "can not find store for calendar";
293 style: "text-align:center",
297 style: "background-color:white;width:50%;margin:250 auto",
301 src: rootURL + '/roojs1/images/ux/lightbox/loading.gif'
312 this.maskEl = Roo.DomHelper.append(this.el.select('.fc-content', true).first(), mark, true);
314 var size = this.el.select('.fc-content', true).first().getSize();
315 this.maskEl.setSize(size.width, size.height);
316 this.maskEl.enableDisplayMode("block");
321 this.store = Roo.factory(this.store, Roo.data);
322 this.store.on('load', this.onLoad, this);
323 this.store.on('beforeload', this.onBeforeLoad, this);
327 this.cells = this.el.select('.fc-day',true);
328 //Roo.log(this.cells);
329 this.textNodes = this.el.query('.fc-day-number');
330 this.cells.addClassOnOver('fc-state-hover');
332 this.el.select('.fc-button-prev',true).on('click', this.showPrevMonth, this);
333 this.el.select('.fc-button-next',true).on('click', this.showNextMonth, this);
334 this.el.select('.fc-button-today',true).on('click', this.showToday, this);
335 this.el.select('.fc-button',true).addClassOnOver('fc-state-hover');
337 this.on('monthchange', this.onMonthChange, this);
339 this.update(new Date().clearTime());
342 resize : function() {
343 var sz = this.el.getSize();
345 this.el.select('.fc-day-header',true).setWidth(sz.width / 7);
346 this.el.select('.fc-day-content div',true).setHeight(34);
351 showPrevMonth : function(e){
352 this.update(this.activeDate.add("mo", -1));
354 showToday : function(e){
355 this.update(new Date().clearTime());
358 showNextMonth : function(e){
359 this.update(this.activeDate.add("mo", 1));
363 showPrevYear : function(){
364 this.update(this.activeDate.add("y", -1));
368 showNextYear : function(){
369 this.update(this.activeDate.add("y", 1));
374 update : function(date)
376 var vd = this.activeDate;
377 this.activeDate = date;
378 // if(vd && this.el){
379 // var t = date.getTime();
380 // if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
381 // Roo.log('using add remove');
383 // this.fireEvent('monthchange', this, date);
385 // this.cells.removeClass("fc-state-highlight");
386 // this.cells.each(function(c){
387 // if(c.dateValue == t){
388 // c.addClass("fc-state-highlight");
389 // setTimeout(function(){
390 // try{c.dom.firstChild.focus();}catch(e){}
400 var days = date.getDaysInMonth();
402 var firstOfMonth = date.getFirstDateOfMonth();
403 var startingPos = firstOfMonth.getDay()-this.startDay;
405 if(startingPos < this.startDay){
409 var pm = date.add(Date.MONTH, -1);
410 var prevStart = pm.getDaysInMonth()-startingPos;
412 this.cells = this.el.select('.fc-day',true);
413 this.textNodes = this.el.query('.fc-day-number');
414 this.cells.addClassOnOver('fc-state-hover');
416 var cells = this.cells.elements;
417 var textEls = this.textNodes;
419 Roo.each(cells, function(cell){
420 cell.removeClass([ 'fc-past', 'fc-other-month', 'fc-future', 'fc-state-highlight', 'fc-state-disabled']);
425 // convert everything to numbers so it's fast
427 var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
430 //Roo.log(prevStart);
432 var today = new Date().clearTime().getTime();
433 var sel = date.clearTime().getTime();
434 var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
435 var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
436 var ddMatch = this.disabledDatesRE;
437 var ddText = this.disabledDatesText;
438 var ddays = this.disabledDays ? this.disabledDays.join("") : false;
439 var ddaysText = this.disabledDaysText;
440 var format = this.format;
442 var setCellClass = function(cal, cell){
444 //Roo.log('set Cell Class');
452 cell.className += " fc-today";
453 cell.className += " fc-state-highlight";
454 cell.title = cal.todayText;
457 // disable highlight in other month..
458 //cell.className += " fc-state-highlight";
463 cell.className = " fc-state-disabled";
464 cell.title = cal.minText;
468 cell.className = " fc-state-disabled";
469 cell.title = cal.maxText;
473 if(ddays.indexOf(d.getDay()) != -1){
474 cell.title = ddaysText;
475 cell.className = " fc-state-disabled";
478 if(ddMatch && format){
479 var fvalue = d.dateFormat(format);
480 if(ddMatch.test(fvalue)){
481 cell.title = ddText.replace("%0", fvalue);
482 cell.className = " fc-state-disabled";
486 if (!cell.initialClassName) {
487 cell.initialClassName = cell.dom.className;
490 cell.dom.className = cell.initialClassName + ' ' + cell.className;
495 for(; i < startingPos; i++) {
496 textEls[i].innerHTML = (++prevStart);
497 d.setDate(d.getDate()+1);
499 cells[i].className = "fc-past fc-other-month";
500 setCellClass(this, cells[i]);
505 for(; i < days; i++){
506 intDay = i - startingPos + 1;
507 textEls[i].innerHTML = (intDay);
508 d.setDate(d.getDate()+1);
510 cells[i].className = ''; // "x-date-active";
511 setCellClass(this, cells[i]);
516 textEls[i].innerHTML = (++extraDays);
517 d.setDate(d.getDate()+1);
519 cells[i].className = "fc-future fc-other-month";
520 setCellClass(this, cells[i]);
523 this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
525 var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
527 this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
528 this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
531 this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
532 this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
535 this.fireEvent('monthchange', this, date);
539 if(!this.internalRender){
540 var main = this.el.dom.firstChild;
541 var w = main.offsetWidth;
542 this.el.setWidth(w + this.el.getBorderWidth("lr"));
543 Roo.fly(main).setWidth(w);
544 this.internalRender = true;
545 // opera does not respect the auto grow header center column
546 // then, after it gets a width opera refuses to recalculate
547 // without a second pass
548 if(Roo.isOpera && !this.secondPass){
549 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
550 this.secondPass = true;
551 this.update.defer(10, this, [date]);
558 findCell : function(dt) {
559 dt = dt.clearTime().getTime();
561 this.cells.each(function(c){
562 //Roo.log("check " +c.dateValue + '?=' + dt);
563 if(c.dateValue == dt){
573 findCells : function(ev) {
574 var s = ev.start.clone().clearTime().getTime();
576 var e= ev.end.clone().clearTime().getTime();
579 this.cells.each(function(c){
580 ////Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
594 // findBestRow: function(cells)
598 // for (var i =0 ; i < cells.length;i++) {
599 // ret = Math.max(cells[i].rows || 0,ret);
606 addItem : function(ev)
608 // look for vertical location slot in
609 var cells = this.findCells(ev);
611 // ev.row = this.findBestRow(cells);
613 // work out the location.
617 for(var i =0; i < cells.length; i++) {
618 // cells[i].row = (typeof(cells[i].row) == 'undefined') ? 1 : (cells[i].row + 1);
626 if (crow.start.getY() == cells[i].getY()) {
645 // if((typeof(cells[0].events) == 'undefined')){
646 // cells[0].events = [];
649 // cells[0].events.push(ev);
650 // ev.rendered = false;
651 // for (var i = 0; i < cells.length;i++) {
652 // cells[i].rows = Math.max(cells[i].rows || 0 , ev.row + 1 );
656 this.calevents.push(ev);
659 clearEvents: function() {
665 Roo.each(this.cells.elements, function(c){
671 Roo.each(this.calevents, function(e) {
672 Roo.each(e.els, function(el) {
673 el.un('mouseenter' ,this.onEventEnter, this);
674 el.un('mouseleave' ,this.onEventLeave, this);
679 Roo.each(Roo.select('.fc-more-event', true).elements, function(e){
685 renderEvents: function()
687 this.cells.each(function(c) {
693 for (var e = 0; e < this.calevents.length; e++) {
695 var ev = this.calevents[e];
696 var cells = ev.cells;
699 for (var j = 0; j < cells.length; j++){
701 if(!cells[j].more.length){
704 if(cells[j].row > 3){
705 cells[j].more.push(ev);
709 cells[j].events.push(ev);
713 // for (var i = 0; i < rows.length; i++){
714 // // how many rows should it span..
717 // cls : 'roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable',
718 // style : 'position: absolute', // left: 387px; width: 121px; top: 359px;
720 // unselectable : "on",
723 // cls: 'fc-event-inner',
727 //// cls: 'fc-event-time',
728 //// html : cells.length > 1 ? '' : ev.time
732 // cls: 'fc-event-title',
733 // html : String.format('{0}', ev.title)
740 // cls: 'ui-resizable-handle ui-resizable-e',
741 // html : '  '
748 // cfg.cls += ' fc-event-start';
750 // if ((i+1) == rows.length) {
751 // cfg.cls += ' fc-event-end';
754 // var ctr = this.el.select('.fc-event-container',true).first();
755 // var cg = ctr.createChild(cfg);
757 // var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
758 // var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
760 // cg.setXY([sbox.x +2, sbox.y +(e * 20)]);
761 // cg.setWidth(ebox.right - sbox.x -2);
763 // cg.on('mouseenter' ,this.onEventEnter, this, ev);
764 // cg.on('mouseleave' ,this.onEventLeave, this, ev);
765 // cg.on('click', this.onEventClick, this, ev);
773 // for (var e = 0; e < this.calevents.length; e++) {
775 // var ev = this.calevents[e];
776 // var cells = ev.cells;
777 // var rows = ev.rows;
779 // for(var i = 0; i < cells.length; i++){
781 // var cbox = this.cells.item(this.cells.indexOf(cells[i]));
783 // if(cells.length < 2 && cbox.rows.length > 3){
784 // cbox.more.push(ev);
788 // cbox.rows.push(ev);
794 // this.cells.each(function(c) {
795 // if(c.more.length && c.more.length == 1){
796 // c.rows.push(c.more.pop());
799 // var r = (c.more.length) ? c.rows.length + 1 : c.rows.length;
800 // c.select('.fc-day-content div',true).first().setHeight(Math.max(34, r * 20));
803 // for (var e = 0; e < c.rows.length; e++){
804 // var ev = c.rows[e];
810 // var cells = ev.cells;
811 // var rows = ev.rows;
813 // for(var i = 0; i < rows.length; i++) {
815 // // how many rows should it span..
818 // cls : 'roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable',
819 // style : 'position: absolute', // left: 387px; width: 121px; top: 359px;
821 // unselectable : "on",
824 // cls: 'fc-event-inner',
828 // // cls: 'fc-event-time',
829 // // html : cells.length > 1 ? '' : ev.time
833 // cls: 'fc-event-title',
834 // html : String.format('{0}', ev.title)
841 // cls: 'ui-resizable-handle ui-resizable-e',
842 // html : '  '
849 // cfg.cls += ' fc-event-start';
851 // if ((i+1) == rows.length) {
852 // cfg.cls += ' fc-event-end';
855 // var ctr = _this.el.select('.fc-event-container',true).first();
856 // var cg = ctr.createChild(cfg);
858 // var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
859 // var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
861 // cg.setXY([sbox.x +2, sbox.y +(e * 20)]);
862 // cg.setWidth(ebox.right - sbox.x -2);
864 // cg.on('mouseenter' ,_this.onEventEnter, _this, ev);
865 // cg.on('mouseleave' ,_this.onEventLeave, _this, ev);
866 // cg.on('click', _this.onEventClick, _this, ev);
870 // ev.rendered = true;
876 // if(c.more.length){
878 // cls : 'fc-more-event roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable fc-event-start fc-event-end',
879 // style : 'position: absolute',
880 // unselectable : "on",
883 // cls: 'fc-event-inner',
887 // cls: 'fc-event-title',
895 // cls: 'ui-resizable-handle ui-resizable-e',
896 // html : '  '
902 // var ctr = _this.el.select('.fc-event-container',true).first();
903 // var cg = ctr.createChild(cfg);
905 // var sbox = c.select('.fc-day-content',true).first().getBox();
906 // var ebox = c.select('.fc-day-content',true).first().getBox();
908 // cg.setXY([sbox.x +2, sbox.y +(c.rows.length * 20)]);
909 // cg.setWidth(ebox.right - sbox.x -2);
911 // cg.on('click', _this.onMoreEventClick, _this, c.more);
921 onEventEnter: function (e, el,event,d) {
922 this.fireEvent('evententer', this, el, event);
925 onEventLeave: function (e, el,event,d) {
926 this.fireEvent('eventleave', this, el, event);
929 onEventClick: function (e, el,event,d) {
930 this.fireEvent('eventclick', this, el, event);
933 onMonthChange: function () {
937 onMoreEventClick: function(e, el, more)
941 this.calpopover.placement = 'right';
942 this.calpopover.setTitle('More');
944 this.calpopover.setContent('');
946 var ctr = this.calpopover.el.select('.popover-content', true).first();
948 Roo.each(more, function(m){
950 cls : 'fc-event-hori fc-event-draggable',
953 var cg = ctr.createChild(cfg);
955 cg.on('click', _this.onEventClick, _this, m);
958 this.calpopover.show(el);
968 if(this.store.getCount() > 0){
969 this.store.data.each(function(d){
972 start: (typeof(d.data.start_dt) === 'string') ? new Date.parseDate(d.data.start_dt, 'Y-m-d H:i:s') : d.data.start_dt,
973 end : (typeof(d.data.end_dt) === 'string') ? new Date.parseDate(d.data.end_dt, 'Y-m-d H:i:s') : d.data.end_dt,
974 time : d.data.start_time,
975 title : d.data.title,
976 description : d.data.description,
984 if(this.calevents.length && this.loadMask){
989 onBeforeLoad: function()