/** * This file contains Calendar utilities * :organization: Logilab * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */CubicWeb.require('python.js');CubicWeb.require('ajax.js');// IMPORTANT NOTE: the variables DAYNAMES AND MONTHNAMES will be added// by cubicweb automatically// dynamically computed (and cached)var_CAL_HEADER=null;TODAY=newDate();/** * .. class:: Calendar * * Calendar (graphical) widget * public methods are : * __init__ : * @param containerId: the DOM node's ID where the calendar will be displayed * @param inputId: which input needs to be updated when a date is selected * @param year, @param month: year and month to be displayed * @param cssclass: CSS class of the calendar widget (default is commandCal) * * show() / hide(): * show or hide the calendar widget * * toggle(): * show (resp. hide) the calendar if it's hidden (resp. displayed) * * displayNextMonth(): (resp. displayPreviousMonth()) * update the calendar to display next (resp. previous) month */Calendar=function(containerId,inputId,year,month,cssclass){this.containerId=containerId;this.inputId=inputId;this.year=year;this.month=month-1;// Javascript's counter starts at 0 for januarythis.cssclass=cssclass||"popupCalendar";this.visible=false;this.domtable=null;this.cellprops={'onclick':function(){dateSelected(this,containerId);},'onmouseover':function(){this.style.fontWeight='bold';},'onmouseout':function(){this.style.fontWeight='normal';}}this.todayprops=jQuery.extend({},this.cellprops,{'class':'today'});this._rowdisplay=function(row){var_td=function(elt){returnTD(this.cellprops,elt);};returnTR(null,map(_td,row));};this._makecell=function(cellinfo){returnTD(cellinfo[0],cellinfo[1]);};/** * .. function:: Calendar._uppercaseFirst(s) * * utility function (the only use for now is inside the calendar) */this._uppercaseFirst=function(s){returns.charAt(0).toUpperCase();}/** * .. function:: Calendar._domForRows(rows) * * accepts the cells data and builds the corresponding TR nodes * * * `rows`, a list of list of couples (daynum, cssprops) */this._domForRows=function(rows){varlines=[];for(i=0;i<rows.length;i++){lines.push(TR(null,map(this._makecell,rows[i])));}returnlines;};/** * .. function:: Calendar._headdisplay(row) * * builds the calendar headers */this._headdisplay=function(row){if(_CAL_HEADER){return_CAL_HEADER;}varself=this;var_th=function(day){returnTH(null,self._uppercaseFirst(day));};returnTR(null,map(_th,DAYNAMES));};this._getrows=function(){varrows=[];varfirstday=newDate(this.year,this.month,1);varstopdate=firstday.nextMonth();varcurdate=firstday.sub(firstday.getRealDay());while(curdate.getTime()<stopdate){varrow=[]for(vari=0;i<7;i++){if(curdate.getMonth()==this.month){props=curdate.equals(TODAY)?this.todayprops:this.cellprops;row.push([props,curdate.getDate()]);}else{row.push([this.cellprops,""]);}curdate.iadd(1);}rows.push(row);}returnrows;}this._makecal=function(){varrows=this._getrows();varmonthname=MONTHNAMES[this.month]+" "+this.year;varprevlink="javascript: togglePreviousMonth('"+this.containerId+"');";varnextlink="javascript: toggleNextMonth('"+this.containerId+"');";this.domtable=TABLE({'class':this.cssclass},THEAD(null,TR(null,TH(null,A({'href':prevlink},"<<")),// IE 6/7 requires colSpan instead of colspanTH({'colSpan':5,'colspan':5,'style':"text-align: center;"},monthname),TH(null,A({'href':nextlink},">>")))),TBODY(null,this._headdisplay(),this._domForRows(rows)));returnthis.domtable;}this._updateDiv=function(){if(!this.domtable){this._makecal();}cw.jqNode(this.containerId).empty().append(this.domtable);// replaceChildNodes($(this.containerId), this.domtable);}this.displayNextMonth=function(){this.domtable=null;if(this.month==11){this.year++;}this.month=(this.month+1)%12;this._updateDiv();}this.displayPreviousMonth=function(){this.domtable=null;if(this.month==0){this.year--;}this.month=(this.month+11)%12;this._updateDiv();}this.show=function(){if(!this.visible){container=cw.jqNode(this.containerId);if(!this.domtable){this._makecal();}container.empty().append(this.domtable);toggleVisibility(container);this.visible=true;}}this.hide=function(event){varself;if(event){self=event.data.self;}else{self=this;}if(self.visible){toggleVisibility(self.containerId);self.visible=false;}}this.toggle=function(){if(this.visible){this.hide();}else{this.show();}}// call hide() when the user explicitly sets the focus on the matching inputcw.jqNode(inputId).bind('focus',{'self':this},this.hide);// connect(inputId, 'onfocus', this, 'hide');};// keep track of each calendar createdCalendar.REGISTRY={};/** * .. function:: toggleCalendar(containerId, inputId, year, month) * * popup / hide calendar associated to `containerId` */functiontoggleCalendar(containerId,inputId,year,month){varcal=Calendar.REGISTRY[containerId];if(!cal){cal=newCalendar(containerId,inputId,year,month);Calendar.REGISTRY[containerId]=cal;}/* hide other calendars */for(containerIdinCalendar.REGISTRY){varothercal=Calendar.REGISTRY[containerId];if(othercal!==cal){othercal.hide();}}cal.toggle();}/** * .. function:: toggleNextMonth(containerId) * * ask for next month to calendar displayed in `containerId` */functiontoggleNextMonth(containerId){varcal=Calendar.REGISTRY[containerId];cal.displayNextMonth();}/** * .. function:: togglePreviousMonth(containerId) * * ask for previous month to calendar displayed in `containerId` */functiontogglePreviousMonth(containerId){varcal=Calendar.REGISTRY[containerId];cal.displayPreviousMonth();}/** * .. function:: dateSelected(cell, containerId) * * Callback called when the user clicked on a cell in the popup calendar */functiondateSelected(cell,containerId){varcal=Calendar.REGISTRY[containerId];varinput=getNode(cal.inputId);// XXX: the use of innerHTML might cause problems, but it seems to be// the only way understood by both IE and Mozilla. Otherwise,// IE accepts innerText and mozilla accepts textContentvarselectedDate=newDate(cal.year,cal.month,cell.innerHTML,12);varxxx=remoteExec("format_date",cw.utils.toISOTimestamp(selectedDate));input.value=xxx;cal.hide();}functionwhichElement(e){vartarg;if(!e){vare=window.event;}if(e.target){targ=e.target;}elseif(e.srcElement){targ=e.srcElement;}if(targ.nodeType==3)// defeat Safari bug{targ=targ.parentNode;}returntarg;}functiongetPosition(element){varleft;vartop;varoffset;// TODO: deal scrollbar positions also!left=element.offsetLeft;top=element.offsetTop;if(element.offsetParent!=null){offset=getPosition(element.offsetParent);left=left+offset[0];top=top+offset[1];}return[left,top];}functiongetMouseInBlock(event){varelt=event.target;varx=event.clientX;vary=event.clientY;varw=elt.clientWidth;varh=elt.clientHeight;varoffset=getPosition(elt);x=1.0*(x-offset[0])/w;y=1.0*(y-offset[1])/h;return[x,y];}functiongetHourFromMouse(event,hmin,hmax){varpos=getMouseInBlock(event);vary=pos[1];returnMath.floor((hmax-hmin)*y+hmin);}functionaddCalendarItem(event,hmin,hmax,year,month,day,duration,baseurl){varhour=getHourFromMouse(event,hmin,hmax);if(0<=hour&&hour<24){baseurl+="&start="+year+"%2F"+month+"%2F"+day+"%20"+hour+":00";baseurl+="&stop="+year+"%2F"+month+"%2F"+day+"%20"+(hour+duration)+":00";stopPropagation(event);window.location.assign(baseurl);returnfalse;}returntrue;}functionstopPropagation(event){event.cancelBubble=true;if(event.stopPropagation)event.stopPropagation();}CubicWeb.provide('calendar.js');