Source: jala/code/Date.js

//
// Jala Project [http://opensvn.csie.org/traccgi/jala]
//
// Copyright 2004 ORF Online und Teletext GmbH
//
// Licensed under the Apache License, Version 2.0 (the ``License'');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ``AS IS'' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// $Revision$
// $LastChangedBy$
// $LastChangedDate$
// $HeadURL$
//

/**
 * @fileoverview Fields and methods of the jala.Date class.
 */

// Define the global namespace for Jala modules
if (!global.jala) {
   global.jala = {};
}


/**
 * HelmaLib dependencies
 */
app.addRepository("modules/core/Date.js");
app.addRepository("modules/helma/Html.js");

/**
 * Constructs a new Renderings object.
 * @class This class provides various convenience
 * methods for rendering purposes.
 * @constructor
 */
jala.Date = function() {
   return this;
};

/**
 * Renders a timestamp as set of DropDown boxes, following the
 * format passed as argument. Every <select>
 * item is prefixed with a string so that it can be retrieved
 * easily from the values of a submitted POST request.
 * @param {String} prefix The prefix to use for all dropdown boxes, eg. "postdate"
 * @param {Date} date A Date object to use as preselection (optional)
 * @param {Object} fmt Array containing one parameter object for every single
 * select box that should be rendered, with the following properties set:
 * <ul>
 * <li>pattern - The date format pattern that should be rendered. Valid
 * patterns are: "dd", "MM", "yyyy", "HH", "ss".</li>
 * <li>firstOption - The string to use as first option, eg.: "choose a day"</li>
 * </ul>
 */
jala.Date.prototype.renderEditor = function(prefix, date, fmt) {
   /**
    * rendering method
    * @private
    */
   var render = function(param, date) {
      switch (param.pattern) {
         case "dd":
         param.offset = 1;
         param.max = 31;
         param.selected = (date ? date.getDate() : null);
         break;

         case "MM":
         param.offset = 1;
         param.max = 12;
         param.selected = (date ? date.getMonth() +1 : null);
         break;

         case "yyyy":
         param.offset = 2002;
         param.max = 20;
         param.selected = (date ? date.getFullYear() : null);
         break;

         case "HH":
         param.offset = 0;
         param.max = 24;
         param.selected = (date ? date.getHours() : null);
         break;

         case "mm":
         param.offset = 0;
         param.max = 60;
         param.selected = (date ? date.getMinutes() : null);
         break;

         case "ss":
         param.offset = 0;
         param.max = 60;
         param.selected = (date ? date.getSeconds() : null);
         break;
      }

      var key = prefix + ":" + param.pattern;
      if (req.data[key])
         param.selected = req.data[key];
      var options = [];
      var opt;
      for (var i=0;i<param.max;i++) {
         opt = (param.offset + i).format("00");
         options[i] = [opt, opt];
      }
      var html = new helma.Html();
      html.dropDown({name: key}, options, param.selected, param.firstOption);
   }

   if (!fmt)
      var fmt = [{pattern: "dd", firstOption: "day"},
                 {pattern: "MM", firstOption: "month"},
                 {pattern: "yyyy", firstOption: "year"},
                 {pattern: "HH", firstOption: "hour"},
                 {pattern: "mm", firstOption: "minute"}];

   for (var i in fmt) {
      render(fmt[i], date);
   }
   return;
};

/**
 * Returns a timestamp as set of dropdown-boxes
 * @see #renderEditor
 * @type String
 */
jala.Date.prototype.renderEditorAsString = function(prefix, date, pattern) {
   res.push();
   this.renderEditor(prefix, date, pattern);
   return res.pop();
};

/**
 * Creates a new instance of jala.Data.Calendar
 * @class This class represents a calendar based based on a grouped
 * collection of HopObjects. It provides several methods for rendering
 * the calendar plus defining locale and timezone settings.
 * @param {HopObject} collection A grouped HopObject collection to work on
 * @returns A newly created jala.Date.Calendar instance
 * @constructor
 */
jala.Date.Calendar = function(collection) {
   var renderer = null;
   var locale = java.util.Locale.getDefault();
   var timezone = java.util.TimeZone.getDefault();
   var hrefFormat = "yyyyMMdd";
   var accessNameFormat = "yyyyMMdd";

   /**
    * Returns the collection this calendar object works on
    * @returns The HopObject collection of this calendar
    * @type HopObject
    */
   this.getCollection = function() {
      return collection;
   };

   /**
    * Sets the renderer to use.
    * @param {Object} r The renderer to use
    * @see #getRenderer
    */
   this.setRenderer = function(r) {
      renderer = r;
      return;
   };

   /**
    * Returns the renderer used by this calendar.
    * @returns The calendar renderer
    * @type Object
    * @see #setRenderer
    */
   this.getRenderer = function() {
      if (!renderer) {
         renderer = new jala.Date.Calendar.Renderer(this);
      }
      return renderer;
   };

   /**
    * Sets the locale to use within this calendar object
    * @param {java.util.Locale} loc The locale to use
    * @see #getLocale
    */
   this.setLocale = function(loc) {
      locale = loc;
      return;
   };

   /**
    * Returns the locale used within this calendar instance.  By default
    * the locale used by this calendar is the default locale of the
    * Java Virtual Machine running Helma.
    * @returns The locale of this calendar
    * @type java.util.Locale
    * @see #setLocale
    */
   this.getLocale = function() {
      return locale;
   };

   /**
    * Sets the locale to use within this calendar object
    * @param {java.util.Locale} loc The locale to use
    * @see #getTimeZone
    */
   this.setTimeZone = function(tz) {
      timezone = tz;
      return;
   };

   /**
    * Returns the locale used within this calendar instance. By default
    * the timezone used by this calendar is the default timezone
    * of the Java Virtual Machine running Helma.
    * @returns The locale of this calendar
    * @type java.util.Locale
    * @see #setTimeZone
    */
   this.getTimeZone = function() {
      return timezone;
   };

   /**
    * Sets the format of the hrefs to render by this calendar
    * to the format pattern passed as argument.
    * @param {String} fmt The date format pattern to use for
    * rendering the href
    * @see #getHrefFormat
    */
   this.setHrefFormat = function(fmt) {
      hrefFormat = fmt;
      return;
   };

   /**
    * Returns the date formatting pattern used to render hrefs. The default
    * format is "yyyyMMdd".
    * @returns The date formatting pattern
    * @type String
    * @see #setHrefFormat
    */
   this.getHrefFormat = function() {
      return hrefFormat;
   };

   /**
    * Sets the format of the group name to use when trying to access
    * child objects of the collection this calendar is operating on.
    * @param {String} fmt The date format pattern to use for
    * accessing child objects
    * @see #getAccessNameFormat
    */
   this.setAccessNameFormat = function(fmt) {
      accessNameFormat = fmt;
      return;
   };

   /**
    * Returns the format of the access name used by this calendar to access
    * child group objects of the collection this calendar is operating on.
    * The default format is "yyyyMMdd".
    * @returns The date formatting pattern used to access child objects
    * @type String
    * @see #setAccessNameFormat
    */
   this.getAccessNameFormat = function() {
      return accessNameFormat;
   };

   return this;
};

/** @ignore */
jala.Date.Calendar.prototype.toString = function() {
   return "[Jala Calendar]";
};

/**
 * Renders the calendar using either a custom renderer defined
 * using {@link #setRenderer} or the default one.
 * @see #setRenderer
 * @see jala.Date.Calendar.Renderer
 */
jala.Date.Calendar.prototype.render = function(today) {
   var renderer = this.getRenderer();
   var collection = this.getCollection();
   var hrefFormat = this.getHrefFormat();
   var accessNameFormat = this.getAccessNameFormat();
   var locale = this.getLocale();
   var timezone = this.getTimeZone();
   var size = collection.size();
   if (size == null)
      return;

   /**
    * private method that creates a date object set
    * to the last date of the previous month or the
    * first date of the next month (if available)
    */
   var prevNextMonth = function(which, dayIndex) {
      var obj;
      if (which == "prev") {
         if (size <= dayIndex || !(obj = collection.get(dayIndex +1)))
            return;
      } else if (which == "next") {
         if (dayIndex == 0 || !(obj = collection.get(dayIndex - 1)))
            return;
      } else {
         return;
      }
      return new Date(obj.groupname.substring(0, 4),
                      obj.groupname.substring(4, 6) -1,
                      obj.groupname.substring(6));
   };

   // create the calendar object used for date calculations
   var cal = java.util.Calendar.getInstance(timezone, locale);
   var firstDayOfWeek = cal.getFirstDayOfWeek();
   var symbols = new java.text.DateFormatSymbols(locale);

   res.push();
   // render the header-row
   res.push();
   var weekdays = symbols.getShortWeekdays();
   for (var i=0;i<7;i++) {
      renderer.renderDayHeader(weekdays[(i+firstDayOfWeek-1)%7+1]);
   }
   renderer.renderRow(res.pop());

   cal.set(java.util.Calendar.DATE, 1);

   // check whether there's a day in path
   // if so, use it to determine the month to render
   if (today) {
      cal.set(java.util.Calendar.YEAR, today.getFullYear());
      cal.set(java.util.Calendar.MONTH, today.getMonth());
   }
   // nr. of empty days in rendered calendar before the first day of month appears
   var pre = (7-firstDayOfWeek+cal.get(java.util.Calendar.DAY_OF_WEEK)) % 7;
   var days = cal.getActualMaximum(java.util.Calendar.DATE);
   var weeks = Math.ceil((pre + days) / 7);
   var daycnt = 1;

   var date = new Date(cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), 1);
   // remember the index of the first and last days within this month.
   // this is needed to optimize previous and next month links.
   var lastDayIndex = Number.MAX_VALUE;
   var firstDayIndex = -1;

   var dayObj, idx, selected;
   for (var i=0;i<weeks;i++) {
      res.push();
      for (var j=0;j<7;j++) {
         if ((i == 0 && j < pre) || daycnt > days) {
            renderer.renderDay(null);
         } else {
            date.setDate(daycnt);
            if ((dayObj = collection.get(date.format(accessNameFormat))) != null) {
               idx = collection.contains(dayObj);
               if (idx > -1) {
                  if  (idx > firstDayIndex) {
                     firstDayIndex = idx;
                  }
                  if (idx < lastDayIndex) {
                     lastDayIndex = idx;
                  }
               }
            }
            selected = (today != null) ? date.equals(today) : false;
            renderer.renderDay(date, dayObj != null, selected);
            daycnt++;
         }
      }
      renderer.renderRow(res.pop());
   }
   var prevMonth = prevNextMonth("prev", firstDayIndex) || null;
   var nextMonth = prevNextMonth("next", lastDayIndex) || null;
   renderer.renderCalendar(date, res.pop(), prevMonth, nextMonth);
   return;
};

/**
 * Returns a rendered calendar
 * @see #renderCalendar
 * @type String
 */
jala.Date.Calendar.prototype.getCalendar = function(today) {
   res.push();
   this.render(today);
   return res.pop();
};

/**
 * Returns a new instance of the default calendar renderer.
 * @class A default renderer to use in conjunction with jala.Date.Calendar
 * @param {jala.Date.Calendar} calendar The calendar utilizing this renderer
 * @returns A newly created instance of jala.Date.Calendar.Renderer
 * @constructor
 */
jala.Date.Calendar.Renderer = function(calendar) {

   /**
    * An instance of helma.Html used for rendering the calendar
    * @type helma.Html
    */
   this.html = new helma.Html();

   /**
    * The calendar utilizing this renderer instance
    * @type jala.Date.Calendar
    */
   this.calendar = calendar;

   return this;
};

/** @ignore */
jala.Date.Calendar.Renderer.prototype.toString = function() {
   return "[Jala Calendar Default Renderer]";
};

/**
 * Renders a single cell in the calendar day header row directly to response.
 * @param {String} text The text to display in the header field.
 */
jala.Date.Calendar.Renderer.prototype.renderDayHeader = function(text) {
   this.html.element("th", text);
   return;
};

/**
 * Renders a single calendar row directly to response.
 * @param {String} row The body of the calendar row.
 */
jala.Date.Calendar.Renderer.prototype.renderRow = function(row) {
   this.html.element("tr", row);
   return;
};

/**
 * Renders a single day within the calendar directly to response.
 * @param {Date} date A date instance representing the day within the calendar.
 * @param {Boolean} isExisting True if there is a child object in the calendar's
 * collection to which the date cell should link to
 * @param {Boolean} isSelected True if this calendar day should be rendered
 * as selected day.
 */
jala.Date.Calendar.Renderer.prototype.renderDay = function(date, isExisting, isSelected) {
   var attr = {"class": "jala-calendar-day day"};
   if (isSelected === true) {
      attr["class"] += " jala-calendar-selected selected";
   }
   this.html.openTag("td", attr);
   if (date != null) {
      var text = date.getDate();
      if (isExisting === true) {
         attr = {"href": this.calendar.getCollection().href() +
                         date.format(this.calendar.getHrefFormat())};
         this.html.link(attr, text);
      } else {
         res.write(text);
      }
   }
   this.html.closeTag("td");
   return;
};

/**
 * Renders a link to the previous or next month's calendar directly to response.
 * @param {Date} date A date object set to the previous or next available
 * month. This can be null in case there is no previous or next month.
 */
jala.Date.Calendar.Renderer.prototype.renderPrevNextLink = function(date) {
   if (date != null) {
      var attr = {"href": this.calendar.getCollection().href() +
                          date.format(this.calendar.getHrefFormat())};
      this.html.link(attr, date.format("MMMM", this.calendar.getLocale()));
   }
   return;
};

/**
 * Renders the calendar directly to response.
 * @param {Date} date A date object representing this calendar's month and year.
 * Please mind that the day will be set to the <em>last</em> date in this
 * month.
 * @param {String} body The rendered calendar weeks including the day header
 * (basically the whole kernel of the table).
 * @param {Date} prevMonth A date object set to the last available date of
 * the previous month. This can be used to render a navigation link to
 * the previous month.
 * @param {Date} nextMonth A date object set to the first available date
 * of the next month. This can be used to render a navigation link to
 * the next month.
 */
jala.Date.Calendar.Renderer.prototype.renderCalendar = function(date, body, prevMonth, nextMonth) {
   var locale = this.calendar.getLocale();
   this.html.openTag("table", {"class": "jala-calendar calendar"});
   this.html.openTag("thead");
   this.html.openTag("tr");
   this.html.openTag("th", {"colspan": 7});
   res.write(date.format("MMMM", locale));
   res.write(' ');
   res.write(date.format("yyyy", locale));
   this.html.closeTag("th");
   this.html.closeTag("tr");
   this.html.closeTag("thead");
   this.html.element("tbody", body);
   this.html.openTag("tfoot");
   this.html.openTag("tr");
   this.html.openTag("td", {"class": "jala-calendar-left left", "colspan": 3});
   this.renderPrevNextLink(prevMonth);
   this.html.closeTag("td");
   this.html.openTag("td");
   this.html.closeTag("td");
   this.html.openTag("td", {"class": "jala-calendar-right right", "colspan": 3});
   this.renderPrevNextLink(nextMonth);
   this.html.closeTag("td");
   this.html.closeTag("tr");
   this.html.closeTag("tfoot");
   this.html.closeTag("table");
   return;
};

/**
 * Default date class instance.
 * @type jala.Date
 * @final
 */
jala.date = new jala.Date();