cubicweb/web/data/cubicweb.calendar.js
changeset 11057 0b59724cb3f2
parent 7529 2fdc310be7cd
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 /**
       
     2  *  This file contains Calendar utilities
       
     3  *  :organization: Logilab
       
     4  *  :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6  */
       
     7 
       
     8 // IMPORTANT NOTE: the variables DAYNAMES AND MONTHNAMES will be added
       
     9 //                 by cubicweb automatically
       
    10 // dynamically computed (and cached)
       
    11 var _CAL_HEADER = null;
       
    12 
       
    13 TODAY = new Date();
       
    14 
       
    15 /**
       
    16  * .. class:: Calendar
       
    17  *
       
    18  *   Calendar (graphical) widget
       
    19  *
       
    20  *   public methods are :
       
    21  *
       
    22  *   __init__ :
       
    23  *    :attr:`containerId`: the DOM node's ID where the calendar will be displayed
       
    24  *    :attr:`inputId`: which input needs to be updated when a date is selected
       
    25  *    :attr:`year`, :attr:`month`: year and month to be displayed
       
    26  *    :attr:`cssclass`: CSS class of the calendar widget (default is 'commandCal')
       
    27  *
       
    28  *   show() / hide():
       
    29  *    show or hide the calendar widget
       
    30  *
       
    31  *   toggle():
       
    32  *    show (resp. hide) the calendar if it's hidden (resp. displayed)
       
    33  *
       
    34  *   displayNextMonth(): (resp. displayPreviousMonth())
       
    35  *    update the calendar to display next (resp. previous) month
       
    36  */
       
    37 Calendar = function(containerId, inputId, year, month, cssclass) {
       
    38     this.containerId = containerId;
       
    39     this.inputId = inputId;
       
    40     this.year = year;
       
    41     this.month = month - 1; // Javascript's counter starts at 0 for january
       
    42     this.cssclass = cssclass || "popupCalendar";
       
    43     this.visible = false;
       
    44     this.domtable = null;
       
    45 
       
    46     this.cellprops = {
       
    47         'onclick': function() {
       
    48             dateSelected(this, containerId);
       
    49         },
       
    50         'onmouseover': function() {
       
    51             this.style.fontWeight = 'bold';
       
    52         },
       
    53         'onmouseout': function() {
       
    54             this.style.fontWeight = 'normal';
       
    55         }
       
    56     };
       
    57 
       
    58     this.todayprops = jQuery.extend({},
       
    59     this.cellprops, {
       
    60         'class': 'today'
       
    61     });
       
    62 
       
    63     this._rowdisplay = function(row) {
       
    64         var _td = function(elt) {
       
    65             return TD(this.cellprops, elt);
       
    66         };
       
    67         return TR(null, $.map(row, _td));
       
    68     };
       
    69 
       
    70     this._makecell = function(cellinfo) {
       
    71         return TD(cellinfo[0], cellinfo[1]);
       
    72     };
       
    73 
       
    74     /**
       
    75      * .. function:: Calendar._uppercaseFirst(s)
       
    76      *
       
    77      *    utility function (the only use for now is inside the calendar)
       
    78      */
       
    79     this._uppercaseFirst = function(s) {
       
    80         return s.charAt(0).toUpperCase();
       
    81     };
       
    82 
       
    83     /**
       
    84      * .. function:: Calendar._domForRows(rows)
       
    85      *
       
    86      *    accepts the cells data and builds the corresponding TR nodes
       
    87      *
       
    88      * * `rows`, a list of list of couples (daynum, cssprops)
       
    89      */
       
    90     this._domForRows = function(rows) {
       
    91         var lines = [];
       
    92         for (i = 0; i < rows.length; i++) {
       
    93             lines.push(TR(null, $.map(rows[i], this._makecell)));
       
    94         }
       
    95         return lines;
       
    96     };
       
    97 
       
    98     /**
       
    99      * .. function:: Calendar._headdisplay(row)
       
   100      *
       
   101      *    builds the calendar headers
       
   102      */
       
   103     this._headdisplay = function(row) {
       
   104         if (_CAL_HEADER) {
       
   105             return _CAL_HEADER;
       
   106         }
       
   107         var self = this;
       
   108         var _th = function(day) {
       
   109             return TH(null, self._uppercaseFirst(day));
       
   110         };
       
   111         return TR(null, $.map(DAYNAMES, _th));
       
   112     };
       
   113 
       
   114     this._getrows = function() {
       
   115         var rows = [];
       
   116         var firstday = new Date(this.year, this.month, 1);
       
   117         var stopdate = firstday.nextMonth();
       
   118         var curdate = firstday.sub(firstday.getRealDay());
       
   119         while (curdate.getTime() < stopdate) {
       
   120             var row = [];
       
   121             for (var i = 0; i < 7; i++) {
       
   122                 if (curdate.getMonth() == this.month) {
       
   123                     props = curdate.equals(TODAY) ? this.todayprops: this.cellprops;
       
   124                     row.push([props, curdate.getDate()]);
       
   125                 } else {
       
   126                     row.push([this.cellprops, ""]);
       
   127                 }
       
   128                 curdate.iadd(1);
       
   129             }
       
   130             rows.push(row);
       
   131         }
       
   132         return rows;
       
   133     };
       
   134 
       
   135     this._makecal = function() {
       
   136         var rows = this._getrows();
       
   137         var monthname = MONTHNAMES[this.month] + " " + this.year;
       
   138         var prevlink = "javascript: togglePreviousMonth('" + this.containerId + "');";
       
   139         var nextlink = "javascript: toggleNextMonth('" + this.containerId + "');";
       
   140         this.domtable = TABLE({
       
   141             'class': this.cssclass
       
   142         },
       
   143         THEAD(null, TR(null, TH(null, A({
       
   144             'href': prevlink
       
   145         },
       
   146         "<<")),
       
   147         // IE 6/7 requires colSpan instead of colspan
       
   148         TH({
       
   149             'colSpan': 5,
       
   150             'colspan': 5,
       
   151             'style': "text-align: center;"
       
   152         },
       
   153         monthname), TH(null, A({
       
   154             'href': nextlink
       
   155         },
       
   156         ">>")))), TBODY(null, this._headdisplay(), this._domForRows(rows)));
       
   157         return this.domtable;
       
   158     };
       
   159 
       
   160     this._updateDiv = function() {
       
   161         if (!this.domtable) {
       
   162             this._makecal();
       
   163         }
       
   164         cw.jqNode(this.containerId).empty().append(this.domtable);
       
   165         // replaceChildNodes($(this.containerId), this.domtable);
       
   166     };
       
   167 
       
   168     this.displayNextMonth = function() {
       
   169         this.domtable = null;
       
   170         if (this.month == 11) {
       
   171             this.year++;
       
   172         }
       
   173         this.month = (this.month + 1) % 12;
       
   174         this._updateDiv();
       
   175     };
       
   176 
       
   177     this.displayPreviousMonth = function() {
       
   178         this.domtable = null;
       
   179         if (this.month == 0) {
       
   180             this.year--;
       
   181         }
       
   182         this.month = (this.month + 11) % 12;
       
   183         this._updateDiv();
       
   184     };
       
   185 
       
   186     this.show = function() {
       
   187         if (!this.visible) {
       
   188             var container = cw.jqNode(this.containerId);
       
   189             if (!this.domtable) {
       
   190                 this._makecal();
       
   191             }
       
   192             container.empty().append(this.domtable);
       
   193             toggleVisibility(container);
       
   194             this.visible = true;
       
   195         }
       
   196     };
       
   197 
       
   198     this.hide = function(event) {
       
   199         var self;
       
   200         if (event) {
       
   201             self = event.data.self;
       
   202         } else {
       
   203             self = this;
       
   204         }
       
   205         if (self.visible) {
       
   206             toggleVisibility(self.containerId);
       
   207             self.visible = false;
       
   208         }
       
   209     };
       
   210 
       
   211     this.toggle = function() {
       
   212         if (this.visible) {
       
   213             this.hide();
       
   214         }
       
   215         else {
       
   216             this.show();
       
   217         }
       
   218     };
       
   219 
       
   220     // call hide() when the user explicitly sets the focus on the matching input
       
   221     cw.jqNode(inputId).bind('focus', {
       
   222         'self': this
       
   223     },
       
   224     this.hide); // connect(inputId, 'onfocus', this, 'hide');
       
   225 };
       
   226 
       
   227 /**
       
   228  * .. data:: Calendar.REGISTRY
       
   229  *
       
   230  *     keep track of each calendar created
       
   231  */
       
   232 Calendar.REGISTRY = {};
       
   233 
       
   234 /**
       
   235  * .. function:: toggleCalendar(containerId, inputId, year, month)
       
   236  *
       
   237  *    popup / hide calendar associated to `containerId`
       
   238  */
       
   239 function toggleCalendar(containerId, inputId, year, month) {
       
   240     var cal = Calendar.REGISTRY[containerId];
       
   241     if (!cal) {
       
   242         cal = new Calendar(containerId, inputId, year, month);
       
   243         Calendar.REGISTRY[containerId] = cal;
       
   244     }
       
   245     /* hide other calendars */
       
   246     for (containerId in Calendar.REGISTRY) {
       
   247         var othercal = Calendar.REGISTRY[containerId];
       
   248         if (othercal !== cal) {
       
   249             othercal.hide();
       
   250         }
       
   251     }
       
   252     cal.toggle();
       
   253 }
       
   254 
       
   255 /**
       
   256  * .. function:: toggleNextMonth(containerId)
       
   257  *
       
   258  *    ask for next month to calendar displayed in `containerId`
       
   259  */
       
   260 function toggleNextMonth(containerId) {
       
   261     var cal = Calendar.REGISTRY[containerId];
       
   262     cal.displayNextMonth();
       
   263 }
       
   264 
       
   265 /**
       
   266  * .. function:: togglePreviousMonth(containerId)
       
   267  *
       
   268  *    ask for previous month to calendar displayed in `containerId`
       
   269  */
       
   270 function togglePreviousMonth(containerId) {
       
   271     var cal = Calendar.REGISTRY[containerId];
       
   272     cal.displayPreviousMonth();
       
   273 }
       
   274 
       
   275 /**
       
   276  * .. function:: dateSelected(cell, containerId)
       
   277  *
       
   278  *    callback called when the user clicked on a cell in the popup calendar
       
   279  */
       
   280 function dateSelected(cell, containerId) {
       
   281     var cal = Calendar.REGISTRY[containerId];
       
   282     var input = cw.getNode(cal.inputId);
       
   283     // XXX: the use of innerHTML might cause problems, but it seems to be
       
   284     //      the only way understood by both IE and Mozilla. Otherwise,
       
   285     //      IE accepts innerText and mozilla accepts textContent
       
   286     var selectedDate = new Date(cal.year, cal.month, cell.innerHTML, 12);
       
   287     input.value = remoteExec("format_date", cw.utils.toISOTimestamp(selectedDate));
       
   288     cal.hide();
       
   289 }
       
   290 
       
   291 function whichElement(e) {
       
   292     var targ;
       
   293     if (!e) {
       
   294         e = window.event;
       
   295     }
       
   296     if (e.target) {
       
   297         targ = e.target;
       
   298     }
       
   299     else if (e.srcElement) {
       
   300         targ = e.srcElement;
       
   301     }
       
   302     if (targ.nodeType == 3) // defeat Safari bug
       
   303     {
       
   304         targ = targ.parentNode;
       
   305     }
       
   306     return targ;
       
   307 }
       
   308 
       
   309 function getPosition(element) {
       
   310     var left;
       
   311     var top;
       
   312     var offset;
       
   313     // TODO: deal scrollbar positions also!
       
   314     left = element.offsetLeft;
       
   315     top = element.offsetTop;
       
   316 
       
   317     if (element.offsetParent != null) {
       
   318         offset = getPosition(element.offsetParent);
       
   319         left = left + offset[0];
       
   320         top = top + offset[1];
       
   321 
       
   322     }
       
   323     return [left, top];
       
   324 }
       
   325 
       
   326 function getMouseInBlock(event) {
       
   327     var elt = event.target;
       
   328     var x = event.clientX;
       
   329     var y = event.clientY;
       
   330     var w = elt.clientWidth;
       
   331     var h = elt.clientHeight;
       
   332     var offset = getPosition(elt);
       
   333 
       
   334     x = 1.0 * (x - offset[0]) / w;
       
   335     y = 1.0 * (y - offset[1]) / h;
       
   336     return [x, y];
       
   337 }
       
   338 function getHourFromMouse(event, hmin, hmax) {
       
   339     var pos = getMouseInBlock(event);
       
   340     var y = pos[1];
       
   341     return Math.floor((hmax - hmin) * y + hmin);
       
   342 }
       
   343 
       
   344 function addCalendarItem(event, hmin, hmax, year, month, day, duration, baseurl) {
       
   345     var hour = getHourFromMouse(event, hmin, hmax);
       
   346 
       
   347     if (0 <= hour && hour < 24) {
       
   348         baseurl += "&start=" + year + "%2F" + month + "%2F" + day + "%20" + hour + ":00";
       
   349         baseurl += "&stop=" + year + "%2F" + month + "%2F" + day + "%20" + (hour + duration) + ":00";
       
   350 
       
   351         stopPropagation(event);
       
   352         window.location.assign(baseurl);
       
   353         return false;
       
   354     }
       
   355     return true;
       
   356 }
       
   357 
       
   358 function stopPropagation(event) {
       
   359     event.cancelBubble = true;
       
   360     if (event.stopPropagation) event.stopPropagation();
       
   361 }