How to patch the Ajax Control Toolkit CalendarExtender to add Decade support and InitialView – Part 3

Now for the code. The vast majority of the code for the Calendar Extender control resides in the CalendarBehavior.js file. This is found in the AjaxControlToolkit source code under the Calendar folder.

To add support for decade, we need to copy the year code and modify it to look like a decade view. So the first thing to do is to find the variables that support year view and copy them.

Original code:

this._years = null;
this._yearsTable = null;
this._yearsBody = null;

Modified code:

this._years = null;
this._yearsTable = null;
this._yearsBody = null;
//S1 - Decades variables
this._decades = null;
this._decadesTable = null;
this._decadesBody = null;
//E1   

Note that I have tagged my code. If I modify code, I start the tag with an S. Where I finish the modification, I finish the tag with an E. In this case, it’s the first modification, so I have tagged it with S1 and E1.

Next, we need to add the modes to the calendar extender control. Go to the lines of code that show the modes.

Original code:

this._modes = {"days" : null, "months" : null, "years" : null};
this._modeOrder = {"days" : 0, "months" : 1, "years" : 2 };

Modified code:

//S1 - Decade view
//    this._modes = {"days" : null, "months" : null, "years" : null};
//    this._modeOrder = {"days" : 0, "months" : 1, "years" : 2 };
this._modes = { "days": null, "months": null, "years": null, "decades": null };
this._modeOrder = { "days": 0, "months": 1, "years": 2, "decades": 3 };
//E1

Note that I have commented out the original code, and added the new code. The new lines show “decades” and put the decades in correct mode order. Not too sure here, but I believe this is needed for animations. Note that commenting out the old code helps me in case I make a mistake and need to go back.  They can always be cleaned up later!

When the calendar is created, it creates the entire content of the calendar in a method called _buildBody. We need to add a method to the calendar extender to support the building of the Decade view. And we do this just below the line that builds the year view.

Original code:

_buildBody: function() {
 /// <summary>
 /// Builds the body region for the calendar
 /// </summary>
 this._body = $common.createElementFromTemplate({
   nodeName: "div",
   properties: { id: this.get_id() + "_body" },
   cssClasses: ["ajax__calendar_body"]
 }, this._popupDiv);
 this._buildDays();
 this._buildMonths();
 this._buildYears();
},

Modified code:

_buildBody: function() {
/// <summary>
/// Builds the body region for the calendar
/// </summary>
  this._body = $common.createElementFromTemplate({
    nodeName: "div",
    properties: { id: this.get_id() + "_body" },
    cssClasses: ["ajax__calendar_body"]
  }, this._popupDiv);
  this._buildDays();
  this._buildMonths();
  this._buildYears();
  //S1 - Create the html elements for the calendar decade view
  this._buildDecades();
  //E1       
},

Next we need to add in the function that actually builds the decades. To do this,  I have copied the method _buildYear and renamed it “_buildDecades”, then I have renamed all the references from “year” to “decade”. The display of a year is basically a table of cells. So I reuse this mechanism to build a table of decade cells. But decades are not the same as years – they take up more space than years. 2000-2009 is not the same as 2000. So after a fair bit of mucking around, I settled on a 3 by 3 grid of decades. Here’s the code:

New code :

_buildDecades: function() {
   /// <summary>
   /// Builds a "decades" view for the calendar
   /// </summary>

   var id = this.get_id();

   this._decades = $common.createElementFromTemplate({
      nodeName: "div",
      properties: { id: id + "_decades" },
      cssClasses: ["ajax__calendar_decades"],
      visible: false
   }, this._body);
   this._modes["decades"] = this._decades;

   this._decadesTable = $common.createElementFromTemplate({
      nodeName: "table",
      properties: {
        id: id + "_decadesTable",
        cellPadding: 0,
        cellSpacing: 0,
        border: 0,
        style: { margin: "auto" }
      }
   }, this._decades);

   this._decadesBody = $common.createElementFromTemplate({ nodeName: "tbody", properties: { id: id + "_decadesBody"} },    this._decadesTable);

   // this is the bit of code that determines the rows (i) and cells (j) that the decades go into.
   // so there are 9 cells.
   for (var i = 0; i < 3; i++) {
      var decadesRow = $common.createElementFromTemplate({ nodeName: "tr" }, this._decadesBody);
      for (var j = 0; j < 3; j++) {
         var decadeCell = $common.createElementFromTemplate({ nodeName: "td" }, decadesRow);
         var decadeDiv = $common.createElementFromTemplate({
            nodeName: "div",
            properties: {
               id: id + "_decade_" + i + "_" + j,
               mode: "decade",
               decade: ((i * 3) + j)
            },
            events: this._cell$delegates,
               cssClasses: &#91;"ajax__calendar_decade"&#93;
         }, decadeCell);
      }
   }
},
&#91;/sourcecode&#93;

Lesser known fact: you can actually add custom attributes to just about any tag. In this case, the tag inside the cell (td) is a div. Using Ajax methods, I simply add an id, mode and decade property. The decade property is used to determine the decade number (0 to 9) that will be used to calculate the decade to be displayed. The current decades to show is actually generated when you click on the Year Title bar. That bit of code is actually required in the _performLayout function. Before we move on, note that I have renamed the css classes to support decades. I will show you the css class mods later. For now, we'll move onto the _performLayout method.

Internally, the calendar extender calls the different views "modes". In the _performLayout function, it selects a mode based on the current visible date. Inside this function, there is a case statement. It is used to render the calendar modes.

Original (pseudo) code:

&#91;sourcecode language='javascript'&#93;
_performLayout: function() {
  /// <summmary>
  /// Updates the various views of the calendar to match the current selected and visible dates
  /// </summary>

//code to initialise the layout and get the current view

switch (this._mode) {
   case "days":
   //code to generate day view
   break;
   case "months":
   //code to generate month view

   break;
   case "years":
   //code to generate year view
   break;
}

//code to set the today bar

}

You’ll need to add a case statement for handling the decade view, as follows:

 Modified code:

 
//S1 – This code generates the decade values for the currently visible decade                         
   case “decades”:
   // the following line is where it rounds down to the start of the current decade
   var minDecade = (Math.floor(visibleDate.getFullYear() / 10 ) * 10 );
   for (var i = 0; i < this._decadesBody.rows.length; i++) {       var row = this._decadesBody.rows[i];       for (var j = 0; j < row.cells.length; j++) {          var cell = row.cells[j].firstChild;   // the following line calculates the decade for the 8 decades prior to the current decade (9 total)          cell.date = new Date(minDecade + ((cell.decade - 8 ) * 10 ), 0, 1, this._hourOffsetForDst);          if (cell.firstChild) {             cell.removeChild(cell.lastChild);          } else {             cell.appendChild(document.createElement("br"));          }          //the following line generates the decade text to be displayed in each cell.          var decadeString = (minDecade + ((cell.decade - 8 ) * 10 )).toString() + "-" + (minDecade + ((cell.decade - 8 ) * 10 ) + 9 ).toString();          cell.appendChild(document.createTextNode(decadeString));          $common.removeCssClasses(cell.parentNode, ["ajax__calendar_other", "ajax__calendar_active"]);          Sys.UI.DomElement.addCssClass(cell.parentNode, this._getCssClass(cell.date, 'y'));       }    }    if (this._title.firstChild) {       this._title.removeChild(this._title.firstChild);    }    //the following sets the text for the title bar. Note that we are at the top level, so clicking on this bar does nothing    this._title.appendChild(document.createTextNode(( minDecade - 80 ).toString() + "-" + ( minDecade + 9 ).toString()));    this._title.date = visibleDate;    //I have enabled the previous and next arrows. They skip 90 years each way - the start of the next batch of decades    this._prevArrow.date = new Date(minDecade - 90, 0, 1, this._hourOffsetForDst);    this._nextArrow.date = new Date(minDecade + 90, 0, 1, this._hourOffsetForDst);    break; //E1 [/sourcecode] Note comments inline. The next bit of code occurs when you click on a cell. When clicking on the cell, it changes the mode to a different mode (if it's year or month) or if it's the day view, it sets the date, closes the calendar and  inserts the formatted date into the text box. Go to the _cell_onclick function, find the following code: [sourcecode language='javascript']  case "title":   switch (this._mode) {      case "days": this._switchMode("months"); break;      case "months": this._switchMode("years"); break;   }   break; [/sourcecode]  We add in the new mode: Modified code: [sourcecode language='javascript']  case "title":   switch (this._mode) {      case "days": this._switchMode("months"); break;      case "months": this._switchMode("years"); break; //S1 - enable the clicking on the title to change to a decade view      case "years": this._switchMode("decades"); break; //E1                      }   break; [/sourcecode] All internal methods, of course, nothing special here. Now, in the same function, _cell_onclick, find the case "year" code block. I copied this block and made modifications to support decade. Original code: [sourcecode language='javascript']  case "year":   if (target.date.getFullYear() != visibleDate.getFullYear()) {      this._visibleDate = target.date;   }   this._switchMode("months");   break; [/sourcecode] Modified code: [sourcecode language='javascript']  case "year":    if (target.date.getFullYear() != visibleDate.getFullYear()) {       this._visibleDate = target.date;    }    this._switchMode("months");    break; //S1 - On clicking a decade, switch to year view        case "decade":   if (target.date.getFullYear() != visibleDate.getFullYear()) {     this._visibleDate = target.date;   }   this._switchMode("years");   break; //E1                        [/sourcecode] Note how similar the year switching code is to the decade switching code . Final code changes are to clean up the generated decade table, cells and divs. Go to the "dispose" function and copy the code that cleans up the year code. Rename all references from "year" to "decade". Original code: [sourcecode language='javascript']  if (this._yearsBody) {   for (var i = 0; i < this._yearsBody.rows.length; i++) {      var row = this._yearsBody.rows[i];      for (var j = 0; j < row.cells.length; j++) {         $common.removeHandlers(row.cells[j].firstChild, this._cell$delegates);      }   }   this._yearsBody = null; } [/sourcecode] Modified code: [sourcecode language='javascript']  if (this._yearsBody) {   for (var i = 0; i < this._yearsBody.rows.length; i++) {      var row = this._yearsBody.rows[i];      for (var j = 0; j < row.cells.length; j++) {         $common.removeHandlers(row.cells[j].firstChild, this._cell$delegates);      }   }   this._yearsBody = null; } //S1 - Clean up decade variables          if (this._decadesBody) {   for (var i = 0; i < this._decadesBody.rows.length; i++) {     var row = this._decadesBody.rows[i];     for (var j = 0; j < row.cells.length; j++) {       $common.removeHandlers(row.cells[j].firstChild, this._cell$delegates);     }   }   this._decadesBody = null; } //E1        [/sourcecode] Note how similar the year dispose code is to the decade dispose code. Finally, you need to add css styles to the calendar to support decades. This is found in the Calendar.css file. After a little bit of tinkering with the styles using IE Dev Toolbar, I settled on the following additional styles, which you can simply add to the bottom of the file. [sourcecode language='css']  /*S1*/ .ajax__calendar_decade {height:44px;width:55px;text-align:center;cursor:pointer;overflow:hidden;} .ajax__calendar .ajax__calendar_decade {border:1px solid #ffffff;} .ajax__calendar .ajax__calendar_active .ajax__calendar_decade {background-color:#edf9ff;border-color:#0066cc;color:#0066cc;} .ajax__calendar .ajax__calendar_other .ajax__calendar_decade {background-color:#ffffff;border-color:#ffffff;color:#646464;} .ajax__calendar .ajax__calendar_hover .ajax__calendar_decade {background-color:#edf9ff;border-color:#daf2fc;color:#0066cc;} /*E1*/ [/sourcecode] And that's it, you now have a decade view! Click for Part 4 >>

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: