devxlogo

Create an Object-Oriented JavaScript Calendar Using the Fa

Create an Object-Oriented JavaScript Calendar Using the Fa

hile doing some shopping one day, I noticed that nearly every retail outlet offers some kind of calendar, each attractively decorated with an image theme. It occurred to me that it would be interesting to create a Web-based calendar where I could control the images displayed for each month. In addition, by displaying the calendar without the images you can make it serve double duty as a date picker component.

The Façade Design Pattern
Design patterns are an interesting concept. In general, design patterns represent an accepted (usually efficient) way of doing something in a programming language. I’ve written about the Model-View-Controller design pattern as it pertains to integrating news feeds. Design patterns are language-agnostic. You’ve probably used a few design patterns before without even realizing it!

A façade is an architectural term that refers to an artificial or deceptive front?a public face?a way to hide less attractive underlying structure and workings. For example, architects might add a marble façade to the street-facing side of a brick building. Similarly, the Façade design pattern is a concept whereby the developer creates a wrapper?a public face?around an intricate object. The wrapper exposes pertinent methods and properties of the underlying object but often hides most of the rest. Façade patterns often make the underlying object easier to use, or give a generic object a public face for a specific purpose.

That’s exactly what the calendar project does. To build the calendar, you’ll use the Façade design pattern to create a wrapper around the built-in JavaScript Date object. The wrapper exposes the Date properties necessary to create a calendar. Note that in this project the wrapper doesn’t actually hide any functionality of the Date object, it just makes it less accessible.



What’s an efficient and flexible way to create a JavaScript-based calendar?



Use the Façade design pattern wrapped around the built-in JavaScript Date object along with a bit of object-oriented JavaScript trickery.

Create the CSS File
You’ll need CSS rules for the month and year banners as well as for the navigation bar at the bottom of the calendar. The calendar holds two types of days?the day that represents today’s date, and all other days. You need to created different CSS rules for each day type so the calendar can display the current date differently from all the other dates. You also need a rule to format the days of the week just beneath the banner. Figure 1 shows a screen shot of the completed calendar in action:

Figure 1. The Completed Calendar: Here’s the completed calendar as it appears in a browser. Users can switch months or years using the arrow links at the bottom of the calendar.

Open your favorite text editor and create the following classes. Save the file as calendar.css:

   .month, .nav{      background-color: navy;      color: white;      font: 10pt sans-serif;   }   .nav{      cursor: pointer;      cursor: hand;   }   .dayHeader{      color: black;      font: 10pt sans-serif;      border-bottom: 1px black solid;      font-weight: bold;   }   .empty{      background-color: white;      border-bottom: 1px black solid;   }   .days{      color: black;      background-color: rgb(235,235,235);;      font: 10pt sans-serif;      border-bottom: 1px black solid;      border-left: 1px black solid;      border-right: 1px black solid;      cursor: pointer;      cursor: hand;   }   .date{      color: maroon;      font: 10pt sans-serif;      font-weight: bold;      border-bottom: 1px black solid;      border-left: 1px black solid;      border-right: 1px black solid;      cursor: pointer;      cursor: hand;   }

Create the Calendar Constructor
Next, open a new file in your text editor and type in the constructor for the calendar object as follows:

   // Constructor   function calendar(id, d, p){      this.id = id;      this.dateObject = d;      this.pix = p;      this.write = writeCalendar;      this.length = getLength();      this.month = d.getMonth();      this.date = d.getDate();      this.day = d.getDay();      this.year = d.getFullYear();      this.getFormattedDate = getFormattedDate;      // get the first day of the month's day      d.setDate(1);      this.firstDay = d.getDay();      // then reset the date object to the correct date      d.setDate(this.date);   }

The constructor takes three parameters: an id value (so that it’s possible to place multiple calendars on a single page), a JavaScript Date object (so a calendar can begin at any arbitrary date), and an array of JavaScript Image objects (which you can manipulate programmatically if need be).

In addition to assigning the three parameters to the appropriate properties, the code adds additional properties and methods to the calendar object that make it functional. All the properties expose values from the wrapped Date object, while the methods provide mechanisms that enable the calendar to display its information. Hiding the Date object within the calendar object is what the Façade design pattern is all about.

Write the Methods
The calendar contains three useful methods: writeCalendar(), getLength(), and getFormattedDate(). To implement getFormattedDate() the code uses two “convenience” arrays that hold the months of the year and the days of the week. Add the following code to your script file:

   var days = new Array('Sunday','Monday','Tuesday',      'Wednesday','Thursday','Friday','Saturday');   var months = new Array('January',      'February','March','April',      'May','June','July','August','September',      'October','November','December');      function getFormattedDate(){      return days[this.day] + ', ' + months[this.month] +         ' ' + this.date + ', ' + this.year;      // return this.month + '/' + this.date + '/' +          this.year;   }

If you’d prefer not to use the convenience arrays, you can get a formatted date from the calendar object itself. I’ve commented out the line in the getFormattedDate() method that does just that. Don’t delete the convenience arrays though, you’ll need them later.

You know the old adage: “Thirty days hath September, April, June and November….” Calculating the length isn’t quite that simple, but it’s close.

You need to know the number of days in any particular month. The getLength() method calculates that value, so the calendar’s length property represents the number of days in the currently displayed month. You know the old adage: “Thirty days hath September, April, June, and November….” Calculating the length isn’t quite that simple, but it’s close. Add the following getLength() method to your script:

   function getLength(){      // thirty days has September...      switch(this.month){         case 1:            if ((this.dateObject.getFullYear()%4==0 &&               this.dateObject.getFullYear()%100!=0) ||               this.dateObject.getFullYear()%400==0)               return 29; // leap year            else               return 28;         case 3:            return 30;         case 5:            return 30;         case 8:            return 30;         case 10:            return 30         default:            return 31;      }   }

The getLength() method correctly determines whether the currently displayed year is a leap year, and adjusts the return value accordingly.

Although the writeCalendar() method looks daunting, remember that you only have to write it once!

The writeCalendar() method is the guts of the calendar object application. It’s responsible for writing the HTML that displays the calendar on the Web page. Essentially, it generates a very long string built from the properties of the calendar object. Although it looks daunting, remember that you only have to write it once! Here’s the writeCalendar() method:

   function writeCalendar(){      var calString = '
'; // write month and year at top of table calString += ''; // write the image -- comment out to hide images calString += ''; // write the month calString += ''; // write a row containing days of the week calString += ''; for(i=0;i' + days[i].substring(0,3) + ''; } // write the body of the calendar calString += ''; // create 6 rows so the calendar doesn't resize for(j=0;j '; }else if(displayNum==this.date){ calString += ''; }else if(displayNum > this.length()){ // Empty cells at bottom of calendar calString += ''; }else{ // the rest of the numbered cells calString += ''; } if(j%7==6){ calString += ''; } } // close the last number row calString += ''; // write the nav row calString += ''; calString += ''; calString += ''; calString += ''; calString += ''; calString += ''; calString += ''; calString += '
' + months[this.month] + ', ' + this.year + '
' + displayNum + ' ' + displayNum + '
 
'; calString += '
'; return calString; }

There’s a lot going on in this method. The first few lines write out containers and then the image to display as well as the Month, Year banner. You can comment out the line that writes the image if you’d like to create a traditional date picker. The code loops through the days array and picks off the first three letters of each day name to display the days of the week. Next, the method writes out the body of the calendar using the calendar object’s firstDay property to determine how many leading empty cells are required. Next, it creates table cells that represent each day of the month, applying the appropriate CSS style for the current day. Finally, it creates the navigation bar at the bottom of the calendar.

Write the Document Object Methods
You may have noticed that each table cell possesses an onClick event handler that points to a function called changeDate() and that the four navigation strings call a function called changeMonth(). You must declare these functions outside of the calendar object even though doing that breaks the object-oriented principle of encapsulation, because the document object raises the events they handle, not the calendar object.

The project uses some JavaScript trickery to get a reference to the current calendar object. Basically, it converts a string parameter into the current calendar object by passing the argument through the eval() function. The caveat is that when you create the calendar object, the id parameter you pass in must be the same as the variable name you declare. This is a small price to pay to keep a reference to the current calendar. Here’s the changeDate() function”

   function changeDate(td,cal){      // Some JavaScript trickery      // Change the cal argument to the existing       // calendar object      // This is why the first argument in the       // constructor must match the variable name      // The cal reference also allows for       // multiple calendars on a page      cal = eval(cal);      document.getElementById(cal.id +          "selected").className = "days";      document.getElementById(cal.id +          "selected").id = "";      td.className = "date";      td.id = cal.id + "selected";      // set the calendar object to the new date      cal.dateObject.setDate(td.firstChild.nodeValue);      cal = new calendar(cal.id,cal.dateObject,cal.pix);      // here is where you could react to a date change—      // I'll just display the formatted date      alert(cal.getFormattedDate());   }

In essence, after converting the cal argument into the calendar object, the changeDate() function uses the td reference to exchange the current date’s CSS class with the newly clicked-on date. It then sets the date of the internal Date object to the value stored in the cell. Finally, for testing purposes, it displays the formatted date in an alert box?but you can choose to handle the date swap any way you choose.

The changeMonth() function moves the calendar to the next or previous month. It uses the eval() trickery you’ve already employed in this function to retain a reference to the current calendar object. The good news is that the Date object and, indirectly, the calendar object is smart enough to roll the year forward and backward when necessary. Here’s the changeMonth() method:

   function changeMonth(mo,cal){      // more trickery!      cal = eval(cal);      // The Date object is smart enough to       // know that it should roll over in December      // when going forward and in January       // when going back      cal.dateObject.setMonth(cal.dateObject.getMonth() + mo);      cal = new calendar(cal.id,cal.dateObject,cal.pix);      cal.formattedDate = cal.getFormattedDate();      document.getElementById('calContainer').innerHTML = cal.write();         }

Save the file as calendar.js in the same folder as the CSS file.

Create the HTML File
At this point, creating an HTML file to display the calendar is pretty simple?the calendar object itself does most of the work. Open a new file in your text editor and create the following HTML file:

         Calendar Demo                           

Note that you need to create the pix array on the HTML page, rather than in the JavaScript file. This makes sense from an object-oriented standpoint?the pictures for any given calendar are page specific. The sample code uses a naming convention and a for loop to create the image array. Save the file in the same directory as the CSS and JavaScript file.

The Façade design pattern works well in this application. It exposes the data available in the JavaScript Date object, removing the necessity for working directly with the Date object itself, and customizing its functionality specifically for the calendar application. Because the calendar uses some W3C DOM methods in the changeDate() method, you'll need to deploy the calendar component in a browser that adheres to the W3C DOM specification. I tested the calendar application in Mozilla Firebird 0.7, Opera 7.1, and Internet Explorer 6 and it worked beautifully in all three browsers.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist