Dynamic Calendar and Appointments
Posted by Doug Fri, 09 Sep 2005 13:30:38 GMT
One of my clients wants to allow her customers to book appointments through her web site. I’m breaking this down into two parts: available time slots and actual appointments. To get more “Web 2.0” pizzaz I’m also using jscalendar from Dynarch. The good news is: I don’t have to wrestle with Time Zones!
First, a bit about jscalendar. I think this is a fine piece of work. I’ve blogged about it before. It’s really cool. Not being a real Javascript pro, it’s been a little tricky to get working. I thought I had everything good to go, but when my client looked at it, she didn’t see anything. Since this calendar is a pretty crucial part of the navigation, that was bad. Turns out I had an extra ’,’ that was breaking the parsing in IE6. I guess sooner or later I’m going to have to get a machine to test IE6 with.
As far as available time slots goes, I’m using a pattern Mark Windholtz told me about. I have a TimeX (Time Expression) class that responds to occurs_on?. The whole goal of the time expression is to respond true when the time expression occurs on a specific date. I subclass this with the various types of time expressions I want to handle. I have:
TimeX::SingleDay– simply occurs on a single dayTimeX::WDay– occurs on a given day of the week with a start and stop dateTimeX::WDays– occurs on a list of days of the week with a start and stop date.
As it turns out TimeX::WDay was just an incremental piece before I got to TimeX::WDays; however, TimeX::WDays contains a list of TimeX::Wday.
I didn’t make the TimeX classes derive from AcriveRecord. What I did was create a TimeExpression class that’s derive from AcriveRecord and contains a TimeX. The TimeX is stored in the database with YAML.dump on before_save and then YAML.load when it’s used. The TimeExpression actually has the available time’s start_time and end_time. I suppose it’d be possible to add a to_vCalendar method on the TimeExpression.
This whole setup seems more complex than it should. Each of the pieces works simply enough though. I think I’m mainly struggling with vocabulary. There are so many pieces that are very similar with similar names it’s easy to get confused. What I’m really struggling with vocabulary-wise is that I’ve named “available time slots” TimeExpression. I don’t really want to live with either TimeExpression or AvailableTimeSlot. I’d like to call it OfficeHours, but that has some pluralization problems. Plus, my client didn’t like that either as her office hours are actually more than these available time slots. I’m sure I’ll refactor this sooner or later.
I stole some code from Typo for doing “quick posts”. The whole interface is driven from a show action that lists the TimeExpressions for a single day. The jscalendar is there to allow her to change days. I’ve got a hidden div with the “new time expression” form in it. Using Effect.BlindUp and Effect.BlindDown I can reveal and hide the form as needed. The form submits to the new action which redirects to the show action when it succeeds. What I haven’t done is passed everything around properly so the errors from the new action are displayed on the resulting show action view.
Here’s the other thing I haven’t done. I haven’t setup any forms for editing the TimeExpression once they are created. I suppose I’ll end up with hidden forms for each of the items. That just seems a little heavy.
One other thing I haven’t done is allow delete to only delete a single occurance of the TimeExpression or all future occurances or all occurances all together. I have the TimeX::WDay class with a list of exception days. I guess it’d be easy enough to just move the end date for the “delete all future occurances” case. What I’m not sure about is the UI for doing this. I’d like to just put :confirm => true on the delete link; but that won’t work for as many options as I have. I need to know JS better.
Oh, and I have no code for the end customers to actually add appointments and then present them with the time slots not booked. Back to work…

Have you seen the temporal expression library available on RubyForge: http://runt.rubyforge.org