JS学习之一个简易的日历控件

  这个日历控件类似于园子用的日历,如下图:

JS学习之一个简易的日历控件

   这种日历控件实现起来不难,下面简单分析下我的思路:

   首先,是该控件的可配置项:

  

复制代码 代码如下:

  ...

  settings:

  {

  firstDayOfWeek: 1,

  baseClass: "calendar",

  curDayClass: "curDay",

  prevMonthCellClass: "prevMonth",

  nextMonthCellClass: "nextMonth",

  curMonthNormalCellClass: "",

  prevNextMonthDaysVisible: true

  },

  ...

  weekDayNames: [],

  ...

  其中有一半是用来控制单元格样式的(不做过多描述),另外几个(firstDayOfWeek,prevNextMonthDaysVisible,weekDayNames),意义如下:

  firstDayOfWeek:日历以星期几做为第一天

  prevNextMonthDaysVisible:是否显示本月之外的日期

  weekDayNames:星期的名称(一个索引从1开始的数组,1处的值将作为周一的显示名称,以此类推)

  接下来,进入生成html代码阶段:

  1.生成日历头:

  

复制代码 代码如下:

  _RenderTitle: function(month, year) {

  var ht = [];

  //日期

  ht.push("<tr>");

  ht.push("<th colspan='7' style='width:100%;'><div style='float:left;width:10%;text-align:center;' id='", this.containerId, "_prevMonth' title='上一月'><</div><div style='float:left;text-align:center;width:80%'>", year, "年", month, "月</div><div style='float:right;width:10%; text-align:center;' id='", this.containerId, "_nextMonth' title='下一月'>></div></th>");

  ht.push("</tr>");

  //星期

  ht.push("<tr>");

  for (var i = 0; i < 7; i++) {

  var day = (i + this.settings.firstDayOfWeek) == 7 ? 7 : (i + this.settings.firstDayOfWeek) % 7;

  ht.push("<th>", this.weekDayNames[day], "</th>")

  }

  ht.push("</tr>");

  return ht.join("");

  },

  日期部分为操作‘按钮'的id使用日历控件容器的id 作为前缀,以保证id唯一。

  星期部分根据firstDayOfWeek的设置来获取weekDayName。这里关键在于判断每个单元格代表星期几,思路很简单:

  var day = (i + this.settings.firstDayOfWeek) == 7 ? 7 : (i + this.settings.firstDayOfWeek) % 7;

  这样就可以取得当前单元格代表的星期了。

  2.生成日历的主要部分:

  

复制代码 代码如下:

  _RenderBody: function(month, year) {

  var date = new Date(year, month - 1, 1);

  var day = date.getDay();

  var dayOfMonth = 1;

  var daysOfPrevMonth = (7 - this.settings.firstDayOfWeek + day) % 7;

  var totalDays = this._GetTotalDays(month, year);

  var totalDaysOfPrevMonth = this._GetToalDaysOfPrevMonth(month, year);

  var ht = [];

  var curDate;

  for (var i = 0; ; i++) {

  curDate = null;

  if (i % 7 == 0) {//新起一行

  ht.push("<tr>");

  }

  ht.push("<td");

  if (i >= daysOfPrevMonth && dayOfMonth <= totalDays) {//本月

  curDate = new Date(year, month - 1, dayOfMonth);

  if (Date.parse(new Date().toDateString()) - curDate == 0) {

  ht.push(" class='", this.settings.curDayClass, "'");

  }

  else {

  ht.push(" class='", this.settings.curMonthNormalCellClass, "'");

  }

  dayOfMonth++;

  }

  else if (i < daysOfPrevMonth) {//上月

  if (this.settings.prevNextMonthDaysVisible) {

  var prevMonth = month;

  var prevYear = year;

  if (month == 1) {

  prevMonth = 12;

  prevYear = prevYear - 1;

  }

  else {

  prevMonth = prevMonth - 1;

  }

  curDate = new Date(prevYear, prevMonth - 1, totalDaysOfPrevMonth - (daysOfPrevMonth - i - 1));

  ht.push(" class='", this.settings.prevMonthCellClass, "'");

  }

  }

  else {//下月

  if (this.settings.prevNextMonthDaysVisible) {

  var nextMonth = month;

  var nextYear = year;

  if (month == 12) {

  nextMonth = 1;

  nextYear = prevYear + 1;

  }

  else {

  nextMonth = nextMonth + 1;

  }

  curDate = new Date(nextYear, nextMonth-1, i - dayOfMonth - daysOfPrevMonth + 2);

  ht.push(" class='", this.settings.nextMonthCellClass, "'");

  }

  }

  ht.push(">");

  ht.push(this._BuildCell(curDate));

  ht.push("</td>");

  if (i % 7 == 6) {//结束一行

  ht.push("</tr>");

  }

  if (i % 7 == 6 && dayOfMonth - 1 >= totalDays) {

  break;

  }

  }

  return ht.join("");

  },

  (1).获取该月一号代表星期几。这样才能判断1号应该放到哪个单元格,也就是该月从哪个单元格开始(创建日期的时候month减了1,这是由于js Date对象本身的特性)。

  (2).定义了一个标识变量 dayOfMonth ,用于控制本月日期显示区域。

  (3).计算要展示的上月的天数以及上月的总天数(不用计算下月要展示的天数和总天数,因为下月要展示的日期是从1开始,最多不会超过6)。

  (4).显示本月的日期:

  条件i >= daysOfPrevMonth && dayOfMonth <= totalDays决定了本月日期的显示区域。

  (5).显示上月日期:

  当i < daysOfPrevMonth 时即为上月日期的显示区域。

  (6). (4)、(5)之外当然就是下月日期的显示区域了。

  (7).何时结束:

  从代码看到for循环是没有终止条件的,因此必须自己决定何时退出循环:

  

复制代码 代码如下:

  if (i % 7 == 6 && dayOfMonth - 1 >= totalDays) {

  break;

  }

  i % 7 == 6表示一行结束, dayOfMonth - 1 >= totalDays表示本月日期已经展示完毕。

  (8).构造curDate:

  curDate代表每个单元格对应的日期。

  在显示本月日期时, curDate = new Date(year, month - 1, dayOfMonth);

  在显示上月日期时, curDate = new Date(prevYear, prevMonth-1, totalDaysOfPrevMonth - (daysOfPrevMonth - i - 1));

  在显示下月日期时, curDate = new Date(nextYear, nextMonth-1, i - dayOfMonth - daysOfPrevMonth + 2),加2是由于i是从0开始,本身就少了1,dayOfMonth 在退出显示本月日期时多加了一次.

  最后,再来看看_BuildCell做了什么事情:

  

复制代码 代码如下:

  _BuildCell: function(curDate) {

  var ht = [];

  if (curDate) {

  for (var j = 0; j < this.dateLinkMappings.length; j++) {

  if (Date.parse(this.dateLinkMappings[j].Date) - curDate == 0) {

  ht.push("<a href='", this.dateLinkMappings[j].Link, "'>", curDate.getDate(), "</a>");

  break;

  }

  }

  if (j == this.dateLinkMappings.length) {

  ht.push(curDate.getDate());

  }

  }

  else {

  ht.push(" ");

  }

  return ht.join("");

  },

  事实上本日历控件的意图是用户可以在初始化时传入日期和该日期对应的链接的映射的数组,也就是this.dateLinkMappings,当构建单元格时若正在构建的日期包含在this.dateLinkMappings里,则将当前单元格构造成<a>形式,否则为普通的文本形式。

  OK,实现逻辑大致如此,篇末看下演示效果:

  前台调用代码如下:

  

复制代码 代码如下:

  var date = new Date();

  var mapping = [];

  mapping.push(new DateLinkMapping("3-22-2010", "javascript:alert(1)"));

  mapping.push(new DateLinkMapping("4-1-2010", "javascript:alert(1)"))

  Calendar.Init(null, mapping);

  Calendar.RenderCalendar("myCalendar", date.getMonth() + 1, date.getFullYear());

  打包下载地址