8 * @class Roo.grid.Calendar
9 * @extends Roo.util.Grid
10 * This class extends the Grid to provide a calendar widget
11 * <br><br>Usage:<pre><code>
12 var grid = new Roo.grid.Calendar("my-container-id", {
15 selModel: mySelectionModel,
16 autoSizeColumns: true,
17 monitorWindowResize: false,
19 eventstore : real data store..
25 * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
26 * The container MUST have some type of size defined for the grid to fill. The container will be
27 * automatically set to position relative if it isn't already.
28 * @param {Object} config A config object that sets properties on this grid.
30 Roo.grid.Calendar = function(container, config){
31 // initialize the container
32 this.container = Roo.get(container);
33 this.container.update("");
34 this.container.setStyle("overflow", "hidden");
35 this.container.addClass('x-grid-container');
37 this.id = this.container.id;
39 Roo.apply(this, config);
40 // check and correct shorthanded configs
44 for (var r = 0;r < 6;r++) {
47 for (var c =0;c < 7;c++) {
51 if (this.eventStore) {
52 this.eventStore= Roo.factory(this.eventStore, Roo.data);
53 this.eventStore.on('load',this.onLoad, this);
54 this.eventStore.on('beforeload',this.clearEvents, this);
58 this.dataSource = new Roo.data.Store({
59 proxy: new Roo.data.MemoryProxy(rows),
60 reader: new Roo.data.ArrayReader({}, [
61 'weekday0', 'weekday1', 'weekday2', 'weekday3', 'weekday4', 'weekday5', 'weekday6' ])
64 this.dataSource.load();
65 this.ds = this.dataSource;
66 this.ds.xmodule = this.xmodule || false;
69 var cellRender = function(v,x,r)
72 '<div class="fc-day fc-widget-content"><div>' +
73 '<div class="fc-event-container"></div>' +
74 '<div class="fc-day-number">{0}</div>'+
76 '<div class="fc-day-content"><div style="position:relative"></div></div>' +
82 this.colModel = new Roo.grid.ColumnModel( [
86 dataIndex : 'weekday0',
93 dataIndex : 'weekday1',
100 dataIndex : 'weekday2',
102 renderer : cellRender
105 xtype: 'ColumnModel',
107 dataIndex : 'weekday3',
108 header : 'Wednesday',
109 renderer : cellRender
112 xtype: 'ColumnModel',
114 dataIndex : 'weekday4',
116 renderer : cellRender
119 xtype: 'ColumnModel',
121 dataIndex : 'weekday5',
123 renderer : cellRender
126 xtype: 'ColumnModel',
128 dataIndex : 'weekday6',
130 renderer : cellRender
133 this.cm = this.colModel;
134 this.cm.xmodule = this.xmodule || false;
138 //this.selModel = new Roo.grid.CellSelectionModel();
139 //this.sm = this.selModel;
140 //this.selModel.init(this);
144 this.container.setWidth(this.width);
148 this.container.setHeight(this.height);
155 * The raw click event for the entire grid.
156 * @param {Roo.EventObject} e
161 * The raw dblclick event for the entire grid.
162 * @param {Roo.EventObject} e
167 * The raw contextmenu event for the entire grid.
168 * @param {Roo.EventObject} e
170 "contextmenu" : true,
173 * The raw mousedown event for the entire grid.
174 * @param {Roo.EventObject} e
179 * The raw mouseup event for the entire grid.
180 * @param {Roo.EventObject} e
185 * The raw mouseover event for the entire grid.
186 * @param {Roo.EventObject} e
191 * The raw mouseout event for the entire grid.
192 * @param {Roo.EventObject} e
197 * The raw keypress event for the entire grid.
198 * @param {Roo.EventObject} e
203 * The raw keydown event for the entire grid.
204 * @param {Roo.EventObject} e
212 * Fires when a cell is clicked
214 * @param {Number} rowIndex
215 * @param {Number} columnIndex
216 * @param {Roo.EventObject} e
220 * @event celldblclick
221 * Fires when a cell is double clicked
223 * @param {Number} rowIndex
224 * @param {Number} columnIndex
225 * @param {Roo.EventObject} e
227 "celldblclick" : true,
230 * Fires when a row is clicked
232 * @param {Number} rowIndex
233 * @param {Roo.EventObject} e
238 * Fires when a row is double clicked
240 * @param {Number} rowIndex
241 * @param {Roo.EventObject} e
243 "rowdblclick" : true,
246 * Fires when a header is clicked
248 * @param {Number} columnIndex
249 * @param {Roo.EventObject} e
251 "headerclick" : true,
253 * @event headerdblclick
254 * Fires when a header cell is double clicked
256 * @param {Number} columnIndex
257 * @param {Roo.EventObject} e
259 "headerdblclick" : true,
261 * @event rowcontextmenu
262 * Fires when a row is right clicked
264 * @param {Number} rowIndex
265 * @param {Roo.EventObject} e
267 "rowcontextmenu" : true,
269 * @event cellcontextmenu
270 * Fires when a cell is right clicked
272 * @param {Number} rowIndex
273 * @param {Number} cellIndex
274 * @param {Roo.EventObject} e
276 "cellcontextmenu" : true,
278 * @event headercontextmenu
279 * Fires when a header is right clicked
281 * @param {Number} columnIndex
282 * @param {Roo.EventObject} e
284 "headercontextmenu" : true,
287 * Fires when the body element is scrolled
288 * @param {Number} scrollLeft
289 * @param {Number} scrollTop
293 * @event columnresize
294 * Fires when the user resizes a column
295 * @param {Number} columnIndex
296 * @param {Number} newSize
298 "columnresize" : true,
301 * Fires when the user moves a column
302 * @param {Number} oldIndex
303 * @param {Number} newIndex
308 * Fires when row(s) start being dragged
310 * @param {Roo.GridDD} dd The drag drop object
311 * @param {event} e The raw browser event
316 * Fires when a drag operation is complete
318 * @param {Roo.GridDD} dd The drag drop object
319 * @param {event} e The raw browser event
324 * Fires when dragged row(s) are dropped on a valid DD target
326 * @param {Roo.GridDD} dd The drag drop object
327 * @param {String} targetId The target drag drop object
328 * @param {event} e The raw browser event
333 * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
335 * @param {Roo.GridDD} dd The drag drop object
336 * @param {String} targetId The target drag drop object
337 * @param {event} e The raw browser event
342 * Fires when the dragged row(s) first cross another DD target while being dragged
344 * @param {Roo.GridDD} dd The drag drop object
345 * @param {String} targetId The target drag drop object
346 * @param {event} e The raw browser event
351 * Fires when the dragged row(s) leave another DD target while being dragged
353 * @param {Roo.GridDD} dd The drag drop object
354 * @param {String} targetId The target drag drop object
355 * @param {event} e The raw browser event
360 * Fires when a row is rendered, so you can change add a style to it.
361 * @param {GridView} gridview The grid view
362 * @param {Object} rowcfg contains record rowIndex and rowClass - set rowClass to add a style.
368 * Fires when the grid is rendered
374 * Fires when a date is selected
375 * @param {DatePicker} this
376 * @param {Date} date The selected date
381 * Fires when the displayed month changes
382 * @param {DatePicker} this
383 * @param {Date} date The selected month
388 * Fires when mouse over an event
389 * @param {Calendar} this
390 * @param {event} Event
395 * Fires when the mouse leaves an
396 * @param {Calendar} this
402 * Fires when the mouse click an
403 * @param {Calendar} this
409 * Fires before each cell is rendered, so you can modify the contents, like cls / title / qtip
410 * @param {Calendar} this
411 * @param {data} data to be modified
417 Roo.grid.Grid.superclass.constructor.call(this);
418 this.on('render', function() {
419 this.view.el.addClass('x-grid-cal');
421 (function() { this.setDate(new Date()); }).defer(100,this); //default today..
425 if (!Roo.grid.Calendar.style) {
426 Roo.grid.Calendar.style = Roo.util.CSS.createStyleSheet({
429 '.x-grid-cal .x-grid-col' : {
430 height: 'auto !important',
431 'vertical-align': 'top'
433 '.x-grid-cal .fc-event-hori' : {
444 Roo.extend(Roo.grid.Calendar, Roo.grid.Grid, {
446 * @cfg {Store} eventStore The store that loads events.
454 monitorWindowResize : false,
457 resizeColumns : function() {
458 var col = (this.view.el.getWidth() / 7) - 3;
459 // loop through cols, and setWidth
460 for(var i =0 ; i < 7 ; i++){
461 this.cm.setColumnWidth(i, col);
464 setDate :function(date) {
468 this.resizeColumns();
469 var vd = this.activeDate;
470 this.activeDate = date;
471 // if(vd && this.el){
472 // var t = date.getTime();
473 // if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
474 // Roo.log('using add remove');
476 // this.fireEvent('monthchange', this, date);
478 // this.cells.removeClass("fc-state-highlight");
479 // this.cells.each(function(c){
480 // if(c.dateValue == t){
481 // c.addClass("fc-state-highlight");
482 // setTimeout(function(){
483 // try{c.dom.firstChild.focus();}catch(e){}
493 var days = date.getDaysInMonth();
495 var firstOfMonth = date.getFirstDateOfMonth();
496 var startingPos = firstOfMonth.getDay()-this.startDay;
498 if(startingPos < this.startDay){
502 var pm = date.add(Date.MONTH, -1);
503 var prevStart = pm.getDaysInMonth()-startingPos;
507 this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
509 this.textNodes = this.view.el.query('.x-grid-row .x-grid-col .x-grid-cell-text');
510 //this.cells.addClassOnOver('fc-state-hover');
512 var cells = this.cells.elements;
513 var textEls = this.textNodes;
515 //Roo.each(cells, function(cell){
516 // cell.removeClass([ 'fc-past', 'fc-other-month', 'fc-future', 'fc-state-highlight', 'fc-state-disabled']);
521 // convert everything to numbers so it's fast
523 var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
526 //Roo.log(prevStart);
528 var today = new Date().clearTime().getTime();
529 var sel = date.clearTime().getTime();
530 var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
531 var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
532 var ddMatch = this.disabledDatesRE;
533 var ddText = this.disabledDatesText;
534 var ddays = this.disabledDays ? this.disabledDays.join("") : false;
535 var ddaysText = this.disabledDaysText;
536 var format = this.format;
538 var setCellClass = function(cal, cell){
540 //Roo.log('set Cell Class');
549 cell.className += " fc-today";
550 cell.className += " fc-state-highlight";
551 cell.title = cal.todayText;
554 // disable highlight in other month..
555 cell.className += " fc-state-highlight";
560 //cell.className = " fc-state-disabled";
561 cell.title = cal.minText;
565 //cell.className = " fc-state-disabled";
566 cell.title = cal.maxText;
570 if(ddays.indexOf(d.getDay()) != -1){
571 // cell.title = ddaysText;
572 // cell.className = " fc-state-disabled";
575 if(ddMatch && format){
576 var fvalue = d.dateFormat(format);
577 if(ddMatch.test(fvalue)){
578 cell.title = ddText.replace("%0", fvalue);
579 cell.className = " fc-state-disabled";
583 if (!cell.initialClassName) {
584 cell.initialClassName = cell.dom.className;
587 cell.dom.className = cell.initialClassName + ' ' + cell.className;
592 for(; i < startingPos; i++) {
593 cells[i].dayName = (++prevStart);
595 d.setDate(d.getDate()+1);
597 //cells[i].className = "fc-past fc-other-month";
598 setCellClass(this, cells[i]);
603 for(; i < days; i++){
604 intDay = i - startingPos + 1;
605 cells[i].dayName = (intDay);
606 d.setDate(d.getDate()+1);
608 cells[i].className = ''; // "x-date-active";
609 setCellClass(this, cells[i]);
614 //textEls[i].innerHTML = (++extraDays);
616 d.setDate(d.getDate()+1);
617 cells[i].dayName = (++extraDays);
618 cells[i].className = "fc-future fc-other-month";
619 setCellClass(this, cells[i]);
622 //this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
624 var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
626 // this will cause all the cells to mis
629 for (var r = 0;r < 6;r++) {
630 for (var c =0;c < 7;c++) {
631 this.ds.getAt(r).set('weekday' + c ,cells[i++].dayName );
635 this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
636 for(i=0;i<cells.length;i++) {
638 this.cells.elements[i].dayName = cells[i].dayName ;
639 this.cells.elements[i].className = cells[i].className;
640 this.cells.elements[i].initialClassName = cells[i].initialClassName ;
641 this.cells.elements[i].title = cells[i].title ;
642 this.cells.elements[i].dateValue = cells[i].dateValue ;
648 //this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
649 //this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
651 ////if(totalRows != 6){
652 //this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
653 // this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
656 this.fireEvent('monthchange', this, date);
661 * Returns the grid's SelectionModel.
662 * @return {SelectionModel}
664 getSelectionModel : function(){
666 this.selModel = new Roo.grid.CellSelectionModel();
668 return this.selModel;
672 this.eventStore.load()
678 findCell : function(dt) {
679 dt = dt.clearTime().getTime();
681 this.cells.each(function(c){
682 //Roo.log("check " +c.dateValue + '?=' + dt);
683 if(c.dateValue == dt){
693 findCells : function(rec) {
694 var s = rec.data.start_dt.clone().clearTime().getTime();
696 var e= rec.data.end_dt.clone().clearTime().getTime();
699 this.cells.each(function(c){
700 ////Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
714 findBestRow: function(cells)
718 for (var i =0 ; i < cells.length;i++) {
719 ret = Math.max(cells[i].rows || 0,ret);
726 addItem : function(rec)
728 // look for vertical location slot in
729 var cells = this.findCells(rec);
731 rec.row = this.findBestRow(cells);
733 // work out the location.
737 for(var i =0; i < cells.length; i++) {
745 if (crow.start.getY() == cells[i].getY()) {
763 for (var i = 0; i < cells.length;i++) {
764 cells[i].rows = Math.max(cells[i].rows || 0 , rec.row + 1 );
771 clearEvents: function() {
773 if (!this.eventStore.getCount()) {
776 // reset number of rows in cells.
777 Roo.each(this.cells.elements, function(c){
781 this.eventStore.each(function(e) {
787 clearEvent : function(ev)
790 Roo.each(ev.els, function(el) {
791 el.un('mouseenter' ,this.onEventEnter, this);
792 el.un('mouseleave' ,this.onEventLeave, this);
800 renderEvent : function(ev,ctr) {
802 ctr = this.view.el.select('.fc-event-container',true).first();
812 var cells = ev.cells;
814 this.fireEvent('eventrender', this, ev);
816 for(var i =0; i < rows.length; i++) {
820 cls += ' fc-event-start';
822 if ((i+1) == rows.length) {
823 cls += ' fc-event-end';
827 // how many rows should it span..
828 var cg = this.eventTmpl.append(ctr,Roo.apply({
834 cg.on('mouseenter' ,this.onEventEnter, this, ev);
835 cg.on('mouseleave' ,this.onEventLeave, this, ev);
836 cg.on('click', this.onEventClick, this, ev);
840 var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
841 var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
844 cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);
845 cg.setWidth(ebox.right - sbox.x -2);
849 renderEvents: function()
851 // first make sure there is enough space..
853 if (!this.eventTmpl) {
854 this.eventTmpl = new Roo.Template(
855 '<div class="roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable {fccls} {cls}" style="position: absolute" unselectable="on">' +
856 '<div class="fc-event-inner">' +
857 '<span class="fc-event-time">{time}</span>' +
858 '<span class="fc-event-title" qtip="{qtip}">{title}</span>' +
860 '<div class="ui-resizable-heandle ui-resizable-e"> </div>' +
868 this.cells.each(function(c) {
869 //Roo.log(c.select('.fc-day-content div',true).first());
870 c.select('.fc-day-content div',true).first().setHeight(Math.max(34, (c.rows || 1) * 20));
873 var ctr = this.view.el.select('.fc-event-container',true).first();
876 this.eventStore.each(function(ev){
878 this.renderEvent(ev);
886 onEventEnter: function (e, el,event,d) {
887 this.fireEvent('evententer', this, el, event);
890 onEventLeave: function (e, el,event,d) {
891 this.fireEvent('eventleave', this, el, event);
894 onEventClick: function (e, el,event,d) {
895 this.fireEvent('eventclick', this, el, event);
898 onMonthChange: function () {
902 onLoad: function () {
904 //Roo.log('calendar onload');
906 if(this.eventStore.getCount() > 0){
910 this.eventStore.each(function(d){
915 if (typeof(add.end_dt) == 'undefined') {
916 Roo.log("Missing End time in calendar data: ");
920 if (typeof(add.start_dt) == 'undefined') {
921 Roo.log("Missing Start time in calendar data: ");
925 add.start_dt = typeof(add.start_dt) == 'string' ? Date.parseDate(add.start_dt,'Y-m-d H:i:s') : add.start_dt,
926 add.end_dt = typeof(add.end_dt) == 'string' ? Date.parseDate(add.end_dt,'Y-m-d H:i:s') : add.end_dt,
927 add.id = add.id || d.id;
928 add.title = add.title || '??';
950 if (!this.view.el.hasClass('course-timesheet')) {
951 this.view.el.addClass('course-timesheet');
958 Roo.log(_this.grid.view.el.getWidth());
961 this.tsStyle = Roo.util.CSS.createStyleSheet({
962 '.course-timesheet .x-grid-row' : {
968 '.course-edit-link' : {
970 'text-overflow' : 'ellipsis',
971 'overflow' : 'hidden',
972 'white-space' : 'nowrap',
978 '.de-act-sup-link' : {
980 'text-decoration' : 'line-through'
984 'text-decoration' : 'line-through'
986 '.course-timesheet .course-highlight' : {
987 'border-top-style': 'dashed !important',
988 'border-bottom-bottom': 'dashed !important'
990 '.course-timesheet .course-item' : {
991 'font-family' : 'tahoma, arial, helvetica',
992 'font-size' : '11px',
993 'overflow' : 'hidden',
994 'padding-left' : '10px',
995 'padding-right' : '10px',
996 'padding-top' : '10px'
1004 monitorWindowResize : false,
1005 cellrenderer : function(v,x,r)
1010 xtype: 'CellSelectionModel',
1017 beforeload : function (_self, options)
1019 options.params = options.params || {};
1020 options.params._month = _this.monthField.getValue();
1021 options.params.limit = 9999;
1022 options.params['sort'] = 'when_dt';
1023 options.params['dir'] = 'ASC';
1024 this.proxy.loadResponse = this.loadResponse;
1026 //this.addColumns();
1028 load : function (_self, records, options)
1030 _this.grid.view.el.select('.course-edit-link', true).on('click', function() {
1031 // if you click on the translation.. you can edit it...
1032 var el = Roo.get(this);
1033 var id = el.dom.getAttribute('data-id');
1034 var d = el.dom.getAttribute('data-date');
1035 var t = el.dom.getAttribute('data-time');
1036 //var id = this.child('span').dom.textContent;
1039 Pman.Dialog.CourseCalendar.show({
1043 productitem_active : id ? 1 : 0
1045 _this.grid.ds.load({});
1050 _this.panel.fireEvent('resize', [ '', '' ]);
1053 loadResponse : function(o, success, response){
1054 // this is overridden on before load..
1056 Roo.log("our code?");
1059 delete this.activeRequest;
1061 this.fireEvent("loadexception", this, o, response);
1062 o.request.callback.call(o.request.scope, null, o.request.arg, false);
1067 result = o.reader.read(response);
1069 Roo.log("load exception?");
1070 this.fireEvent("loadexception", this, o, response, e);
1071 o.request.callback.call(o.request.scope, null, o.request.arg, false);
1074 Roo.log("ready...");
1075 // loop through result.records;
1076 // and set this.tdate[date] = [] << array of records..
1078 Roo.each(result.records, function(r){
1080 if(typeof(_this.tdata[r.data.when_dt.format('j')]) == 'undefined'){
1081 _this.tdata[r.data.when_dt.format('j')] = [];
1083 _this.tdata[r.data.when_dt.format('j')].push(r.data);
1086 //Roo.log(_this.tdata);
1088 result.records = [];
1089 result.totalRecords = 6;
1091 // let's generate some duumy records for the rows.
1092 //var st = _this.dateField.getValue();
1094 // work out monday..
1095 //st = st.add(Date.DAY, -1 * st.format('w'));
1097 var date = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
1099 var firstOfMonth = date.getFirstDayOfMonth();
1100 var days = date.getDaysInMonth();
1102 var firstAdded = false;
1103 for (var i = 0; i < result.totalRecords ; i++) {
1104 //var d= st.add(Date.DAY, i);
1107 for(var w = 0 ; w < 7 ; w++){
1108 if(!firstAdded && firstOfMonth != w){
1115 var dd = (d > 0 && d < 10) ? "0"+d : d;
1116 row['weekday'+w] = String.format(
1117 '<span style="font-size: 16px;"><b>{0}</b></span>'+
1118 '<span class="course-edit-link" style="color:blue;" data-id="0" data-date="{1}"> Add New</span>',
1120 date.format('Y-m-')+dd
1123 if(typeof(_this.tdata[d]) != 'undefined'){
1124 Roo.each(_this.tdata[d], function(r){
1128 var desc = (r.productitem_id_descrip) ? r.productitem_id_descrip : '';
1129 if(r.parent_id*1>0){
1130 is_sub = (r.productitem_id_visible*1 < 1) ? 'de-act-sup-link' :'sub-link';
1133 if(r.productitem_id_visible*1 < 1 && r.parent_id*1 < 1){
1134 deactive = 'de-act-link';
1137 row['weekday'+w] += String.format(
1138 '<br /><span class="course-edit-link {3} {4}" qtip="{5}" data-id="{0}">{2} - {1}</span>',
1140 r.product_id_name, //1
1141 r.when_dt.format('h:ia'), //2
1151 // only do this if something added..
1153 result.records.push(_this.grid.dataSource.reader.newRow(row));
1157 // push it twice. (second one with an hour..
1161 this.fireEvent("load", this, o, o.request.arg);
1162 o.request.callback.call(o.request.scope, result, o.request.arg, true);
1164 sortInfo : {field: 'when_dt', direction : 'ASC' },
1169 url : baseURL + '/Roo/Shop_course.php'
1172 xtype: 'JsonReader',
1189 'name': 'parent_id',
1193 'name': 'product_id',
1197 'name': 'productitem_id',
1215 click : function (_self, e)
1217 var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
1218 sd.setMonth(sd.getMonth()-1);
1219 _this.monthField.setValue(sd.format('Y-m-d'));
1220 _this.grid.ds.load({});
1230 xtype: 'MonthField',
1233 render : function (_self)
1235 _this.monthField = _self;
1236 // _this.monthField.set today
1238 select : function (combo, date)
1240 _this.grid.ds.load({});
1243 value : (function() { return new Date(); })()
1252 text : "Blue: in-active, green: in-active sup-event, red: de-active, purple: de-active sup-event"
1262 click : function (_self, e)
1264 var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
1265 sd.setMonth(sd.getMonth()+1);
1266 _this.monthField.setValue(sd.format('Y-m-d'));
1267 _this.grid.ds.load({});