1b98c44f7c0591ea0b244d87f3339b3684798e7f
[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.cells = this.el.select('.fc-day',true);
274         this.textNodes = this.el.query('.fc-day-number');
275         this.cells.addClassOnOver('fc-state-hover');
276         
277         this.el.select('.fc-button-prev',true).on('click', this.showPrevMonth, this);
278         this.el.select('.fc-button-next',true).on('click', this.showNextMonth, this);
279         this.el.select('.fc-button-today',true).on('click', this.showToday, this);
280         this.el.select('.fc-button',true).addClassOnOver('fc-state-hover');
281         
282         this.on('monthchange', this.onMonthChange, this);
283         
284         this.update(new Date().clearTime());
285     },
286     
287     resize : function() {
288         var sz  = this.el.getSize();
289         
290         this.el.select('.fc-day-header',true).setWidth(sz.width / 7);
291         this.el.select('.fc-day-content div',true).setHeight(34);
292     },
293     
294     
295     // private
296     showPrevMonth : function(e){
297         this.update(this.activeDate.add("mo", -1));
298     },
299     showToday : function(e){
300         this.update(new Date().clearTime());
301     },
302     // private
303     showNextMonth : function(e){
304         this.update(this.activeDate.add("mo", 1));
305     },
306
307     // private
308     showPrevYear : function(){
309         this.update(this.activeDate.add("y", -1));
310     },
311
312     // private
313     showNextYear : function(){
314         this.update(this.activeDate.add("y", 1));
315     },
316
317     
318    // private
319     update : function(date)
320     {
321         var vd = this.activeDate;
322         this.activeDate = date;
323         if(vd && this.el){
324             var t = date.getTime();
325             if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
326                 Roo.log('using add remove');
327                 this.cells.removeClass("fc-state-highlight");
328                 this.cells.each(function(c){
329                    if(c.dateValue == t){
330                        c.addClass("fc-state-highlight");
331                        setTimeout(function(){
332                             try{c.dom.firstChild.focus();}catch(e){}
333                        }, 50);
334                        return false;
335                    }
336                    return true;
337                 });
338                 return;
339             }
340         }
341         
342         var days = date.getDaysInMonth();
343         
344         var firstOfMonth = date.getFirstDateOfMonth();
345         var startingPos = firstOfMonth.getDay()-this.startDay;
346
347         if(startingPos < this.startDay){
348             startingPos += 7;
349         }
350
351         var pm = date.add("mo", -1);
352         var prevStart = pm.getDaysInMonth()-startingPos;
353
354         var cells = this.cells.elements;
355         var textEls = this.textNodes;
356         days += startingPos;
357
358         // convert everything to numbers so it's fast
359         var day = 86400000;
360         var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
361         var today = new Date().clearTime().getTime();
362         var sel = date.clearTime().getTime();
363         var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
364         var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
365         var ddMatch = this.disabledDatesRE;
366         var ddText = this.disabledDatesText;
367         var ddays = this.disabledDays ? this.disabledDays.join("") : false;
368         var ddaysText = this.disabledDaysText;
369         var format = this.format;
370
371         var setCellClass = function(cal, cell){
372             cell.title = "";
373             var t = d.getTime();
374             
375             cell.dateValue = t;
376             if(t == today){
377                 cell.className += " fc-today";
378                 cell.title = cal.todayText;
379             }
380             if(t == sel){
381                 cell.className += " fc-state-highlight";
382                 //setTimeout(function(){
383                 //    try{cell.firstChild.focus();}catch(e){}
384                 //}, 50);
385             }
386             // disabling
387             if(t < min) {
388                 cell.className = " fc-state-disabled";
389                 cell.title = cal.minText;
390                 return;
391             }
392             if(t > max) {
393                 cell.className = " fc-state-disabled";
394                 cell.title = cal.maxText;
395                 return;
396             }
397             if(ddays){
398                 if(ddays.indexOf(d.getDay()) != -1){
399                     cell.title = ddaysText;
400                     cell.className = " fc-state-disabled";
401                 }
402             }
403             if(ddMatch && format){
404                 var fvalue = d.dateFormat(format);
405                 if(ddMatch.test(fvalue)){
406                     cell.title = ddText.replace("%0", fvalue);
407                     cell.className = " fc-state-disabled";
408                 }
409             }
410             if (!cell.initialClassName) {
411                 cell.initialClassName = cell.dom.className;
412             }
413             cell.dom.className = cell.initialClassName  + ' ' +  cell.className;
414         };
415
416         var i = 0;
417         for(; i < startingPos; i++) {
418             textEls[i].innerHTML = (++prevStart);
419             d.setDate(d.getDate()+1);
420             cells[i].className = "fc-past fc-other-month";
421             setCellClass(this, cells[i]);
422         }
423         for(; i < days; i++){
424             intDay = i - startingPos + 1;
425             textEls[i].innerHTML = (intDay);
426             d.setDate(d.getDate()+1);
427             cells[i].className = ''; // "x-date-active";
428             setCellClass(this, cells[i]);
429         }
430         var extraDays = 0;
431         
432         for(; i < 42; i++) {
433             textEls[i].innerHTML = (++extraDays);
434             d.setDate(d.getDate()+1);
435             cells[i].className = "fc-future fc-other-month";
436             setCellClass(this, cells[i]);
437         }
438         
439         this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
440         
441         var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
442         
443         this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
444         this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
445         
446         if(totalRows != 6){
447             this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
448             this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
449         }
450         
451         this.fireEvent('monthchange', this, date);
452         
453         
454         /*
455         if(!this.internalRender){
456             var main = this.el.dom.firstChild;
457             var w = main.offsetWidth;
458             this.el.setWidth(w + this.el.getBorderWidth("lr"));
459             Roo.fly(main).setWidth(w);
460             this.internalRender = true;
461             // opera does not respect the auto grow header center column
462             // then, after it gets a width opera refuses to recalculate
463             // without a second pass
464             if(Roo.isOpera && !this.secondPass){
465                 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
466                 this.secondPass = true;
467                 this.update.defer(10, this, [date]);
468             }
469         }
470         */
471         
472     },
473     
474     findCell : function(dt) {
475         dt = dt.clearTime().getTime();
476         var ret = false;
477         this.cells.each(function(c){
478             //Roo.log("check " +c.dateValue + '?=' + dt);
479             if(c.dateValue == dt){
480                 ret = c;
481                 return false;
482             }
483             return true;
484         });
485         Roo.log(ret);
486         return ret;
487     },
488     
489     findCells : function(ev) {
490         var s = ev.start.clone().clearTime().getTime();
491         var e= ev.end.clone().clearTime().getTime();
492         var ret = [];
493         this.cells.each(function(c){
494             //Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
495             
496             if(c.dateValue > e){
497                 return ;
498             }
499             if(c.dateValue < s){
500                 return ;
501             }
502             ret.push(c);
503         });
504         //Roo.log(ret);
505         return ret;    
506     },
507     
508     findBestRow: function(cells)
509     {
510         var ret = 0;
511         
512         for (var i =0 ; i < cells.length;i++) {
513             ret  = Math.max(cells[i].rows || 0,ret);
514         }
515         return ret;
516         //d.setDate(d.ev()+1);
517         
518     },
519     
520     
521     addItem : function(ev)
522     {
523         // look for vertical location slot in
524         var cells = this.findCells(ev);
525         
526         ev.row = this.findBestRow(cells);
527         
528         // work out the location.
529         
530         var crow = false;
531         var rows = [];
532         for(var i =0; i < cells.length; i++) {
533             if (!crow) {
534                 crow = {
535                     start : cells[i],
536                     end :  cells[i]
537                 };
538                 continue;
539             }
540             if (crow.start.getY() == cells[i].getY()) {
541                 // on same row.
542                 crow.end = cells[i];
543                 continue;
544             }
545             // different row.
546             rows.push(crow);
547             crow = {
548                 start: cells[i],
549                 end : cells[i]
550             };
551             
552         }
553         
554         rows.push(crow);
555         ev.els = [];
556         ev.rows = rows;
557         ev.cells = cells;
558         for (var i = 0; i < cells.length;i++) {
559             cells[i].rows = Math.max(cells[i].rows || 0 , ev.row + 1 );
560             
561         }
562         
563         this.calevents.push(ev);
564     },
565     
566     clearEvents: function() {
567         
568         if(!this.calevents){
569             return;
570         }
571         
572         Roo.each(this.cells.elements, function(c){
573             c.rows = 0;
574         });
575         
576         Roo.each(this.calevents, function(e) {
577             Roo.each(e.els, function(el) {
578                 el.un('mouseenter' ,this.onEventEnter, this);
579                 el.un('mouseleave' ,this.onEventLeave, this);
580                 el.remove();
581             },this);
582         },this);
583         
584     },
585     
586     renderEvents: function()
587     {   
588         // first make sure there is enough space..
589         
590         this.cells.each(function(c) {
591 //            Roo.log(c.select('.fc-day-content div',true).first(), Math.max(34, c.rows * 20));
592             c.select('.fc-day-content div',true).first().setHeight(Math.max(34, c.rows * 20));
593         });
594         
595         for (var e = 0; e < this.calevents.length; e++) {
596             var ev = this.calevents[e];
597             var cells = ev.cells;
598             var rows = ev.rows;
599             var boxHeight = 0;
600             
601             for(var i =0; i < rows.length; i++) {
602                 
603                  
604                 // how many rows should it span..
605                 
606                 var  cfg = {
607                     cls : 'roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable',
608                     style : 'position: absolute', // left: 387px; width: 121px; top: 359px;
609                     
610                     unselectable : "on",
611                     cn : [
612                         {
613                             cls: 'fc-event-inner',
614                             cn : [
615                                 {
616                                   tag:'span',
617                                   cls: 'fc-event-time',
618                                   html : cells.length > 1 ? '' : '7pm'
619                                 },
620                                 {
621                                   tag:'span',
622                                   cls: 'fc-event-title',
623                                   html : String.format('{0}', ev.title)
624                                 }
625                                 
626                                 
627                             ]
628                         },
629                         {
630                             cls: 'ui-resizable-handle ui-resizable-e',
631                             html : '&nbsp;&nbsp;&nbsp'
632                         }
633                         
634                     ]
635                 };
636                 if (i == 0) {
637                     cfg.cls += ' fc-event-start';
638                 }
639                 if ((i+1) == rows.length) {
640                     cfg.cls += ' fc-event-end';
641                 }
642                 
643                 var ctr = this.el.select('.fc-event-container',true).first();
644                 var cg = ctr.createChild(cfg);
645                 
646                 cg.on('mouseenter' ,this.onEventEnter, this);
647                 cg.on('mouseleave' ,this.onEventLeave, this);
648                 
649                 ev.els.push(cg);
650                 var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
651                 var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
652                 //Roo.log(cg);
653                 cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);    
654                 cg.setWidth(ebox.right - sbox.x -2);
655                 
656                 boxHeight += Roo.log(cg.getHeight());
657             }
658             
659         }
660         
661     },
662     
663     onEventEnter: function (e, el,c,d) {
664         this.fireEvent('evententer', this, el);
665     },
666     
667     onEventLeave: function (e, el,c,d) {
668         this.fireEvent('eventleave', this, el);
669     },
670     
671     onMonthChange: function () {
672         this.store.load();
673     },
674     
675     onLoad: function () {
676         
677         this.clearEvents();
678         
679         this.calevents = [];
680         var cal = this;
681         if(this.store.getCount() > 0){
682             this.store.data.each(function(d){
683                cal.addItem({
684                     start: new Date(d.data.start_dt),
685                     end : new Date(d.data.end_dt),
686                     title : d.data.title
687                 });
688             });
689         }
690         Roo.log(this.calevents);
691         this.renderEvents();
692     }
693 });
694
695  
696