By Peter Bell

Creating an OO Calendar: Part II

One of the nice things about this blog (I think) is that I try to show the real process I'm sure most of us go as developers through rather than just presenting "perfect" answers or code once I'm done.

So, if you're interested in a passable solution to the calendar to see what can be done in 90 minutes, read on. Refactoring definitely required, but the client is happy and I can get back to my OOPSLA presentation, so that'll do me for this week . . .

First, I needed to be able to get events from the database. I added a getByMonth() to my service class:

<cffunction name="getByMonth" returntype="any" hint="I return an IBO based on the filter provided in the order requested containing a single page of data based on the page number and number of records per page." output="false">
   <cfargument name="Month" type="string" required="false" default="" hint="The month to view.">   
   <cfargument name="Year" type="string" required="false" default="" hint="The year to view.">   
   <cfargument name="Filter" type="string" required="true" hint="The filter to use.">   
   <cfargument name="Order" type="string" required="false" default="#DefaultOrderBy#" hint="The order by using 1..n property names with optional desc after each.">   
   <cfargument name="PropertyNameList" type="string" required="false" default="#DefaultPropertyNameList#" hint="An optional comma delimited list of the property names to load the IBO with.">   
   <cfargument name="IncludeHidden" type="boolean" required="false" default="false" hint="Whether to include hidden records.">   
   <cfargument name="IncludeUnapproved" type="boolean" required="false" default="false" hint="Whether to include upapproved records.">   
   <cfargument name="IncludeDeleted" type="boolean" required="false" default="false" hint="Whether to include deleted records in the list.">   
      var Object = "";
      var MonthStart = "";
      var MonthEnd = "";
      arguments.Order = "StartDate";
      If (Len(arguments.Filter))
      arguments.Filter = arguments.Filter & " AND ";
      If (Len(arguments.Month) LT 1)
      {arguments.Month = DatePart("m", now());};
      If (Len(arguments.Year) LT 1)
      {arguments.Year = DatePart("yyyy", now());};
      MonthStart = DateFormat(CreateDate(Year, Month, 1), "MM/DD/YYYY");
      MonthEnd = DateFormat(CreateDate(Year, Month, DaysInMonth(MonthStart)), "MM/DD/YYYY");
      // Get all events that start before end of month and end after start of month       arguments.Filter = arguments.Filter & "(StartDate <= '#MonthEnd#' AND EndDate >= '#MonthStart#')";
      Object = getByFilter(argumentCollection=arguments);
      Object.classSet("MonthStart", MonthStart);
      Object.classSet("MonthEnd", MonthEnd);
   <cfreturn Object>

I then added a getDateEvents() to the IBO to return a recordset (for now) of events for a given date:

<cffunction name="getDateEvents" returntype="any" output="false" hint="I return the events for a date.">
   <cfargument name="Date" type="string" required="true" hint="The date to return events for.">
   <cfset var ReturnValue = "">
   <cfquery name="ReturnValue" dbtype="query">
       SELECT *
       FROM variables.recordset
       WHERE StartDate <= '#Date#' AND EndDate >= '#Date#'
   <cfreturn ReturnValue>

I then hacked together a quick display which works but needs some serious refactoring. The calendar is being used by two screens, so right now each sets three properties before calling:

<cfset BaseEventURL = "#Page.get("URL")#?action=viewIndustryEvent&ReturnAction=selectFormat&area=#Input.get("Area")#&EventID=">
<cfset EventID = "IndustryEventID">
<cfset EventTitle = "SeriesTitle">
<cfinclude template="event-calendar.cfm">

Then the calendar code is almost a straight lift from Ben's post with just a few changes for my use case:

<cfset EventList = Data.Events.reset()>
<cfset MonthStart = EventList.classGet("MonthStart")>
<cfset MonthEnd = EventList.classGet("MonthEnd")>
<style type="text/css">
td {
font: 11px verdana ;

table.month {}

table.month tr.dayheader td {
background-color: ##8EDB00 ;
border: 1px solid ##308800 ;
border-bottom-width: 2px ;
color: ##E3FB8E ;
font-weight: bold ;
padding: 5px 0px 5px 0px ;
text-align: center ;

table.month td {
background-color: ##E3FB8E ;
border: 1px solid ##999999 ;
color: ##308800 ;
padding: 5px 0px 5px 0px ;
text-align: center ;

table.month {
background-color: ##C8F821 ;
color: ##666666 ;
font-weight: bold ;

   <table width="100%" cellspacing="2" class="month" border="1">
   <col width="12%" />
   <col width="15%" />
   <col width="15%" />
   <col width="15%" />
   <col width="15%" />
   <col width="15%" />
   <col width="13%" />
   <tr class="dayheader">
   <tr class="day">
<cfloop index="intDate" from="#MonthStart#" to="#MonthEnd#" step="1">
<cfset DateIncrement = IntDate - MonthStart>
<cfset ThisDate = DateFormat(DateAdd("d", DateIncrement, MonthStart), "MM/DD/YYYY")>

   <td valign="top"<cfif ThisDate EQ DateFormat(Now(), "MM/DD/YYYY")> class="today"</cfif>>
      #Day( intDate )#<br />
      <cfset DayEventList = EventList.getDateEvents(ThisDate)>
      <cfloop query="DayEventList">
      <a href="#BaseEventURL##evaluate(EventID)#">#Evaluate(EventTitle)#</a><br />
   <cfif ((DayOfWeek( intDate ) EQ 7) AND (intDate LT MonthEnd))>
      <tr class="day">


Clearly this needs some refactoring. I could turn the calendar display into a custom tag, but I'd rather refactor my controllers to load the properties into the business object and obviously I need to refactor the evaluates in the display, but a composed IBO of events would just allow me to call get("Title") and get("ID") and get("URL") for each event, so I could encapsulate the metadata required to customize this for different objects.

Lots of other ugly bits and pieces, but it seems to work quite well. Initially, I had planned to use some kind of array or struct to store the events. The more I played with it, the more I realized that a recordset was the perfect format to interact with the events within the IBO in terms of being able to filter and order by using Query of Query to get a subset of results for each day. I'll have to see how that performs, but if it is reasonable, I'll probably use the recordset within the IBO for storing and loading events into and then QoQ for returning the events that include a given day.

Any thoughts?

Actually a solution to your constant query (I know Ben asnwered you before, but i find my approach much simpler)

<cfset var your_query = "">
<cfset var monthStart = CreateDate(ARGUMENTS.ano, ARGUMENTS.mes, 1)>
<cfset var monthEnd = CreateDate(ARGUMENTS.ano, ARGUMENTS.mes, DaysInMonth(fechaInicioMes))>
<cfset var resultado = structNew()>

<!--- Devolver la cantidad de alarmas de la base de datos --->
<cfquery name="your_query" datasource="#APPLICATION.sistema.datasource#">
   SELECT COUNT(eventID) as nEvents
       , DAY(eventDate) as dayToMatchWithHTML
   FROM Events
   WHERE eventDate BETWEEN <cfqueryparam cfsqltype="cf_sql_date" value="#monthStart#">
                   AND <cfqueryparam cfsqltype="cf_sql_date" value="#monthEnd#">
   GROUP BY DAY(eventDate)            

That will return the day,a nd the amount of events in it, you can simple highlight the days that show one or more events with a simple "cfif"
# Posted By Raul Riera | 7/26/07 12:26 PM
Oops my preivous code had some spanish in it

<cfset var your_query = "">
<cfset var monthStart = CreateDate(ARGUMENTS.year, ARGUMENTS.month, 1)>
<cfset var monthEnd = CreateDate(ARGUMENTS.year, ARGUMENTS.month, DaysInMonth(monthStart ))>
<cfset var resultado = structNew()>
# Posted By Raul Riera | 7/26/07 12:27 PM

Many thanks! Of course, this returns the number of events per day, so for what I'm doing right now (actually displaying the events for each day) it doesn't quite fit, but thanks for sharing it anyway I have a feeling I could use this idea elsewhere!
# Posted By Peter Bell | 7/27/07 4:58 PM
Hi Peter,
One of the challenges I had with dates for charts was generating days with no events so I wrote a table based UDF you can use to simplify things. A simple count by date ignores dates with no events but the following UDF allows you to get a solid base for a left join so your charts can represent temporal gaps:
# Posted By Adam Howitt | 8/3/07 9:02 AM
Hi Adam,

Thanks for the comment! Trust life is treating you well in the Windy city? Thanks for the link. Will definitely check it out.
# Posted By Peter Bell | 8/3/07 3:19 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.