Roo/bootstrap/Calendar.js
[roojs1] / Roo / bootstrap / Calendar.js
1 /*
2  * - LGPL
3  *
4  * based on jquery fullcalendar
5  * 
6  */
7
8
9 /**
10  * @class Roo.bootstrap.Calendar
11  * @extends Roo.bootstrap.Component
12  * Bootstrap Calendar class
13     
14  * @constructor
15  * Create a new Container
16  * @param {Object} config The config object
17  */
18
19 Roo.bootstrap.Calendar = function(config){
20     Roo.bootstrap.Calendar.superclass.constructor.call(this, config);
21      this.addEvents({
22         /**
23              * @event select
24              * Fires when a date is selected
25              * @param {DatePicker} this
26              * @param {Date} date The selected date
27              */
28         'select': true,
29         /**
30              * @event monthchange
31              * Fires when the displayed month changes 
32              * @param {DatePicker} this
33              * @param {Date} date The selected month
34              */
35         'monthchange': true,
36         /**
37              * @event evententer
38              * Fires when mouse over an event
39              * @param {Calendar} this
40              * @param {event} Event
41              */
42         'evententer': true,
43         /**
44              * @event eventleave
45              * Fires when the mouse leaves an
46              * @param {Calendar} this
47              * @param {event}
48              */
49         'eventleave': true
50         
51     });
52
53 };
54
55 Roo.extend(Roo.bootstrap.Calendar, Roo.bootstrap.Component,  {
56     
57      /**
58      * @cfg {Number} startDay
59      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
60      */
61     startDay : 0,
62       
63     getAutoCreate : function(){
64         
65         
66         fc_button = function(name, corner, style, content ) {
67             return Roo.apply({},{
68                 tag : 'span',
69                 cls : 'fc-button fc-button-'+name+' fc-state-default ' + 
70                          (corner.length ?
71                             'fc-corner-' + corner.split(' ').join(' fc-corner-') :
72                             ''
73                         ),
74                 html : '<SPAN class="fc-text-'+style+ '">'+content +'</SPAN>',
75                 unselectable: 'on'
76             });
77         };
78         
79         var header = {
80             tag : 'table',
81             cls : 'fc-header',
82             style : 'width:100%',
83             cn : [
84                 {
85                     tag: 'tr',
86                     cn : [
87                         {
88                             tag : 'td',
89                             cls : 'fc-header-left',
90                             cn : [
91                                 fc_button('prev', 'left', 'arrow', '&#8249;' ),
92                                 fc_button('next', 'right', 'arrow', '&#8250;' ),
93                                 { tag: 'span', cls: 'fc-header-space' },
94                                 fc_button('today', 'left right', '', 'today' )  // neds state disabled..
95                                 
96                                 
97                             ]
98                         },
99                         
100                         {
101                             tag : 'td',
102                             cls : 'fc-header-center',
103                             cn : [
104                                 {
105                                     tag: 'span',
106                                     cls: 'fc-header-title',
107                                     cn : {
108                                         tag: 'H2',
109                                         html : 'month / year'
110                                     }
111                                 }
112                                 
113                             ]
114                         },
115                         {
116                             tag : 'td',
117                             cls : 'fc-header-right',
118                             cn : [
119                           /*      fc_button('month', 'left', '', 'month' ),
120                                 fc_button('week', '', '', 'week' ),
121                                 fc_button('day', 'right', '', 'day' )
122                             */    
123                                 
124                             ]
125                         }
126                         
127                     ]
128                 }
129             ]
130         };
131         
132        
133         var cal_heads = function() {
134             var ret = [];
135             // fixme - handle this.
136             
137             for (var i =0; i < Date.dayNames.length; i++) {
138                 var d = Date.dayNames[i];
139                 ret.push({
140                     tag: 'th',
141                     cls : 'fc-day-header fc-' + d.substring(0,3).toLowerCase() + ' fc-widget-header',
142                     html : d.substring(0,3)
143                 });
144                 
145             }
146             ret[0].cls += ' fc-first';
147             ret[6].cls += ' fc-last';
148             return ret;
149         };
150         var cal_cell = function(n) {
151             return  {
152                 tag: 'td',
153                 cls : 'fc-day fc-'+n + ' fc-widget-content', ///fc-other-month fc-past
154                 cn : [
155                     {
156                         cn : [
157                             {
158                                 cls: 'fc-day-number',
159                                 html: 'D'
160                             },
161                             {
162                                 cls: 'fc-day-content',
163                              
164                                 cn : [
165                                      {
166                                         style: 'position: relative;' // height: 17px;
167                                     }
168                                 ]
169                             }
170                             
171                             
172                         ]
173                     }
174                 ]
175                 
176             }
177         };
178         var cal_rows = function() {
179             
180             var ret = []
181             for (var r = 0; r < 6; r++) {
182                 var row= {
183                     tag : 'tr',
184                     cls : 'fc-week',
185                     cn : []
186                 };
187                 
188                 for (var i =0; i < Date.dayNames.length; i++) {
189                     var d = Date.dayNames[i];
190                     row.cn.push(cal_cell(d.substring(0,3).toLowerCase()));
191
192                 }
193                 row.cn[0].cls+=' fc-first';
194                 row.cn[0].cn[0].style = 'min-height:90px';
195                 row.cn[6].cls+=' fc-last';
196                 ret.push(row);
197                 
198             }
199             ret[0].cls += ' fc-first';
200             ret[4].cls += ' fc-prev-last';
201             ret[5].cls += ' fc-last';
202             return ret;
203             
204         };
205         
206         var cal_table = {
207             tag: 'table',
208             cls: 'fc-border-separate',
209             style : 'width:100%',
210             cellspacing  : 0,
211             cn : [
212                 { 
213                     tag: 'thead',
214                     cn : [
215                         { 
216                             tag: 'tr',
217                             cls : 'fc-first fc-last',
218                             cn : cal_heads()
219                         }
220                     ]
221                 },
222                 { 
223                     tag: 'tbody',
224                     cn : cal_rows()
225                 }
226                   
227             ]
228         };
229          
230          var cfg = {
231             cls : 'fc fc-ltr',
232             cn : [
233                 header,
234                 {
235                     cls : 'fc-content',
236                     style : "position: relative;",
237                     cn : [
238                         {
239                             cls : 'fc-view fc-view-month fc-grid',
240                             style : 'position: relative',
241                             unselectable : 'on',
242                             cn : [
243                                 {
244                                     cls : 'fc-event-container',
245                                     style : 'position:absolute;z-index:8;top:0;left:0;'
246                                 },
247                                 cal_table
248                             ]
249                         }
250                     ]
251     
252                 }
253            ] 
254             
255         };
256         
257          
258         
259         return cfg;
260     },
261     
262     
263     initEvents : function()
264     {
265         if(!this.store){
266             throw "can not find store for combo";
267         }
268         
269         this.store = Roo.factory(this.store, Roo.data);
270         this.store.on('load', this.onLoad, this);
271         
272         this.resize();
273         this.el.select('.fc-button-prev',true).on('click', this.showPrevMonth, this);
274         this.el.select('.fc-button-next',true).on('click', this.showNextMonth, this);
275         this.el.select('.fc-button-today',true).on('click', this.showToday, this);
276         this.el.select('.fc-button',true).addClassOnOver('fc-state-hover');
277         
278         this.on('monthchange', this.onMonthChange, this);
279         
280         this.update(new Date().clearTime());
281     },
282     
283     resize : function() {
284         var sz  = this.el.getSize();
285         
286         this.el.select('.fc-day-header',true).setWidth(sz.width / 7);
287         this.el.select('.fc-day-content div',true).setHeight(34);
288     },
289     
290     
291     // private
292     showPrevMonth : function(e){
293         this.update(this.activeDate.add("mo", -1));
294     },
295     showToday : function(e){
296         this.update(new Date().clearTime());
297     },
298     // private
299     showNextMonth : function(e){
300         this.update(this.activeDate.add("mo", 1));
301     },
302
303     // private
304     showPrevYear : function(){
305         this.update(this.activeDate.add("y", -1));
306     },
307
308     // private
309     showNextYear : function(){
310         this.update(this.activeDate.add("y", 1));
311     },
312
313     
314    // private
315     update : function(date)
316     {
317         var vd = this.activeDate;
318         this.activeDate = date;
319         if(vd && this.el){
320             var t = date.getTime();
321             if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
322                 Roo.log('using add remove');
323                 this.cells.removeClass("fc-state-highlight");
324                 this.cells.each(function(c){
325                    if(c.dateValue == t){
326                        c.addClass("fc-state-highlight");
327                        setTimeout(function(){
328                             try{c.dom.firstChild.focus();}catch(e){}
329                        }, 50);
330                        return false;
331                    }
332                    return true;
333                 });
334                 return;
335             }
336         }
337         
338         this.cells = this.el.select('.fc-day',true);
339         Roo.log('cells');
340         Roo.log(this.cells);
341         this.textNodes = this.el.query('.fc-day-number');
342         this.cells.addClassOnOver('fc-state-hover');
343         
344         var days = date.getDaysInMonth();
345         
346         var firstOfMonth = date.getFirstDateOfMonth();
347         var startingPos = firstOfMonth.getDay()-this.startDay;
348
349         if(startingPos < this.startDay){
350             startingPos += 7;
351         }
352
353         var pm = date.add("mo", -1);
354         var prevStart = pm.getDaysInMonth()-startingPos;
355
356         var cells = this.cells.elements;
357         var textEls = this.textNodes;
358         days += startingPos;
359
360         // convert everything to numbers so it's fast
361         var day = 86400000;
362         var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
363         var today = new Date().clearTime().getTime();
364         var sel = date.clearTime().getTime();
365         var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
366         var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
367         var ddMatch = this.disabledDatesRE;
368         var ddText = this.disabledDatesText;
369         var ddays = this.disabledDays ? this.disabledDays.join("") : false;
370         var ddaysText = this.disabledDaysText;
371         var format = this.format;
372
373         var setCellClass = function(cal, cell){
374             cell.title = "";
375             var t = d.getTime();
376             
377             cell.dateValue = t;
378             if(t == today){
379                 cell.className += " fc-today";
380                 cell.title = cal.todayText;
381             }
382             if(t == sel){
383                 cell.className += " fc-state-highlight";
384                 //setTimeout(function(){
385                 //    try{cell.firstChild.focus();}catch(e){}
386                 //}, 50);
387             }
388             // disabling
389             if(t < min) {
390                 cell.className = " fc-state-disabled";
391                 cell.title = cal.minText;
392                 return;
393             }
394             if(t > max) {
395                 cell.className = " fc-state-disabled";
396                 cell.title = cal.maxText;
397                 return;
398             }
399             if(ddays){
400                 if(ddays.indexOf(d.getDay()) != -1){
401                     cell.title = ddaysText;
402                     cell.className = " fc-state-disabled";
403                 }
404             }
405             if(ddMatch && format){
406                 var fvalue = d.dateFormat(format);
407                 if(ddMatch.test(fvalue)){
408                     cell.title = ddText.replace("%0", fvalue);
409                     cell.className = " fc-state-disabled";
410                 }
411             }
412             if (!cell.initialClassName) {
413                 cell.initialClassName = cell.dom.className;
414             }
415             cell.dom.className = cell.initialClassName  + ' ' +  cell.className;
416         };
417
418         var i = 0;
419         for(; i < startingPos; i++) {
420             textEls[i].innerHTML = (++prevStart);
421             d.setDate(d.getDate()+1);
422             cells[i].className = "fc-past fc-other-month";
423             setCellClass(this, cells[i]);
424         }
425         for(; i < days; i++){
426             intDay = i - startingPos + 1;
427             textEls[i].innerHTML = (intDay);
428             d.setDate(d.getDate()+1);
429             cells[i].className = ''; // "x-date-active";
430             setCellClass(this, cells[i]);
431         }
432         var extraDays = 0;
433         
434         for(; i < 42; i++) {
435             textEls[i].innerHTML = (++extraDays);
436             d.setDate(d.getDate()+1);
437             cells[i].className = "fc-future fc-other-month";
438             setCellClass(this, cells[i]);
439         }
440         
441         this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
442         
443         var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
444         
445         this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
446         this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
447         
448         if(totalRows != 6){
449             this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
450             this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
451         }
452         
453         this.fireEvent('monthchange', this, date);
454         
455         
456         /*
457         if(!this.internalRender){
458             var main = this.el.dom.firstChild;
459             var w = main.offsetWidth;
460             this.el.setWidth(w + this.el.getBorderWidth("lr"));
461             Roo.fly(main).setWidth(w);
462             this.internalRender = true;
463             // opera does not respect the auto grow header center column
464             // then, after it gets a width opera refuses to recalculate
465             // without a second pass
466             if(Roo.isOpera && !this.secondPass){
467                 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
468                 this.secondPass = true;
469                 this.update.defer(10, this, [date]);
470             }
471         }
472         */
473         
474     },
475     
476     findCell : function(dt) {
477         dt = dt.clearTime().getTime();
478         var ret = false;
479         this.cells.each(function(c){
480             //Roo.log("check " +c.dateValue + '?=' + dt);
481             if(c.dateValue == dt){
482                 ret = c;
483                 return false;
484             }
485             return true;
486         });
487         Roo.log(ret);
488         return ret;
489     },
490     
491     findCells : function(ev) {
492         var s = ev.start.clone().clearTime().getTime();
493         var e= ev.end.clone().clearTime().getTime();
494         var ret = [];
495         this.cells.each(function(c){
496             //Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
497             
498             if(c.dateValue > e){
499                 return ;
500             }
501             if(c.dateValue < s){
502                 return ;
503             }
504             ret.push(c);
505         });
506         //Roo.log(ret);
507         return ret;    
508     },
509     
510     findBestRow: function(cells)
511     {
512         var ret = 0;
513         
514         for (var i =0 ; i < cells.length;i++) {
515             ret  = Math.max(cells[i].rows || 0,ret);
516         }
517         return ret;
518         //d.setDate(d.ev()+1);
519         
520     },
521     
522     
523     addItem : function(ev)
524     {
525         // look for vertical location slot in
526         var cells = this.findCells(ev);
527         
528         ev.row = this.findBestRow(cells);
529         
530         // work out the location.
531         
532         var crow = false;
533         var rows = [];
534         for(var i =0; i < cells.length; i++) {
535             if (!crow) {
536                 crow = {
537                     start : cells[i],
538                     end :  cells[i]
539                 };
540                 continue;
541             }
542             if (crow.start.getY() == cells[i].getY()) {
543                 // on same row.
544                 crow.end = cells[i];
545                 continue;
546             }
547             // different row.
548             rows.push(crow);
549             crow = {
550                 start: cells[i],
551                 end : cells[i]
552             };
553             
554         }
555         
556         rows.push(crow);
557         ev.els = [];
558         ev.rows = rows;
559         ev.cells = cells;
560         for (var i = 0; i < cells.length;i++) {
561             cells[i].rows = Math.max(cells[i].rows || 0 , ev.row + 1 );
562             
563         }
564         
565         this.calevents.push(ev);
566     },
567     
568     clearEvents: function() {
569         
570         if(!this.calevents){
571             return;
572         }
573         
574         Roo.each(this.calevents, function(e) {
575             Roo.each(e.els, function(el) {
576                 el.un('mouseenter' ,this.onEventEnter, this);
577                 el.un('mouseleave' ,this.onEventLeave, this);
578                 el.remove();
579             },this);
580         },this);
581         
582     },
583     
584     renderEvents: function()
585     {   
586         // first make sure there is enough space..
587         this.cells.each(function(c) {
588 //            Roo.log(c.select('.fc-day-content div',true).first(), Math.max(34, c.rows * 20));
589             c.select('.fc-day-content div',true).first().setHeight(Math.max(34, c.rows * 20));
590         });
591         
592         for (var e = 0; e < this.calevents.length; e++) {
593             var ev = this.calevents[e];
594             var cells = ev.cells;
595             var rows = ev.rows;
596             
597             for(var i =0; i < rows.length; i++) {
598                 
599                  
600                 // how many rows should it span..
601                 
602                 var  cfg = {
603                     cls : 'roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable',
604                     style : 'position: absolute', // left: 387px; width: 121px; top: 359px;
605                     
606                     unselectable : "on",
607                     cn : [
608                         {
609                             cls: 'fc-event-inner',
610                             cn : [
611                                 {
612                                   tag:'span',
613                                   cls: 'fc-event-time',
614                                   html : cells.length > 1 ? '' : '7pm'
615                                 },
616                                 {
617                                   tag:'span',
618                                   cls: 'fc-event-title',
619                                   html : String.format('{0}', ev.title)
620                                 }
621                                 
622                                 
623                             ]
624                         },
625                         {
626                             cls: 'ui-resizable-handle ui-resizable-e',
627                             html : '&nbsp;&nbsp;&nbsp'
628                         }
629                         
630                     ]
631                 };
632                 if (i == 0) {
633                     cfg.cls += ' fc-event-start';
634                 }
635                 if ((i+1) == rows.length) {
636                     cfg.cls += ' fc-event-end';
637                 }
638                 
639                 var ctr = this.el.select('.fc-event-container',true).first();
640                 var cg = ctr.createChild(cfg);
641                 
642                 cg.on('mouseenter' ,this.onEventEnter, this);
643                 cg.on('mouseleave' ,this.onEventLeave, this);
644                 
645                 ev.els.push(cg);
646                 var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
647                 var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
648                 //Roo.log(cg);
649                 cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);    
650                 cg.setWidth(ebox.right - sbox.x -2);
651             }
652             
653         }
654         
655     },
656     
657     onEventEnter: function (e, el,c,d) {
658         this.fireEvent('evententer', this, el);
659     },
660     
661     onEventLeave: function (e, el,c,d) {
662         this.fireEvent('eventleave', this, el);
663     },
664     
665     onMonthChange: function () {
666         this.store.load();
667     },
668     
669     onLoad: function () {
670         
671         this.clearEvents();
672         
673         this.calevents = [];
674         var cal = this;
675         if(this.store.getCount() > 0){
676             this.store.data.each(function(d){
677                cal.addItem({
678                     start: new Date(d.data.start_dt),
679                     end : new Date(d.data.end_dt),
680                     title : d.data.title
681                 });
682             });
683         }
684         Roo.log(this.calevents);
685 //        this.renderEvents();
686     }
687 });
688
689  
690