Problem: I needed a dynamic appointment scheduler that will instantly run a date chosen by a user against all the available time slots for that date. The scheduler then needs to dynamically hide the time slots that have already been chosen.
Solution: jQuery's datepicker plugin and .ajax() function.
I'll demonstrate this in an MVC context. All code can be had on Github.
First, we need to create an appointment model. So in the terminal run:
We use a string datatype for 'date' because we'll want to present the calendar date format ('yy-mm-dd') immediately to the user. Setting 'hour:string' allows the records to have leading zeros which allows us to set four digits for any hour e.g. 1300 and 0900, which in turn makes string pattern matching easier. We'll use military time to help facilitate this as well.
Now, for our appointment class:
The controller contains our three needed RESTful routes. If you are confused about REST and how it relates to Rails, read this first followed by this. Our controller:
For our routes, we need only add the following:
Here, no appointment-specific routes need to be hardcoded - an advantage of RESTful architecture.
SAVING A DATE
Upon visiting '/appointments/new', a user sees nothing but the jQuery calendar because we've hidden the 'hour_picker' and 'appointment_form' divs using jQuery's .hide() function which is triggered upon page load. document.ready function calls the .datepicker() function rendering the calendar. Clicking a datebox in the calendar itself triggers the 'onSelect:' callback function.
The 'onSelect:' function reverses the user interface by hiding the calendar and showing the hour_picker div. 'dateText' is the date value chosen by our user - we pass this value to our 'findHours' function which does nothing for now because no dates have ever been chosen. We'll revisit this when we try to save the same date twice.
The uncloaked hour_picker div offers the user a list of standard business-day hours.
Here, each table row is a button which triggers the 'nextPage' function (seen below) with two parameters - the button's ID and a string that is a duplicate of the button's class. Matching the button's class to a 'nextPage' parameter will allow us to block, hide, or disable more buttons in the future in order to prevent duplication.
Finally, the user is shown submission form. The values have been added to hidden fields to prevent the user from changing them at this point in the process. Instead, to allow the user to review their selections before submission, we use jQuery's .append() to peg the chosen date and hour to specific divs.
TRYING TO SAVE A DUPLICATE DATE
So now a different user wants to book that exact same date and time. They hit a calendar date, say February 21, 2012, and, along with moving the user to the next phase, the onclick listener triggers the 'findHours()' function which is no longer useless:
This function dynamically checks the user-chosen date for any of its hours that may have already been booked. We pass the chosen_date to jQuery's very useful.ajax() function which creates a GET request by attaching the chosen_date value to the URI string. We set the 'cache' property to false causing a current timestamp to automatically be attached to the URI - we don't use the timestamp here, but it may be useful later. Also, we don't want to cache the data should the user revisit the scheduler in the same session.
.ajax() is a higher-order function, meaning that if certain conditions are met, it returns a 'success' callback. This callback takes the hours of our user-chosen-date, returned as a comma-delimited sting to the index view (more on this later) - and converts the hours into the elements of a new array through string manipulation.
By sending a GET request to /../appointments, we call the index method in our appointments_controller. This method...
Speaking of new.html.erb, you'll notice this snippet..
<div id = "hidden_hour_div"></div>
..is where we'll peg the string returned from our index method. The second part of the findHours() function changes this string into an array. We then cycle through the array, attaching a '.' in front of each element so that jQuery can recognize each element as a particular class in our view and hide those classes accordingly. Why do we add the '.' after the fact? Because ID and class tokens must begin with a hexadecimal ([0-9A-Za-z]) so we can't call our <tr> classes '.0900' for instance.
An alternate strategy for findHours() is to have the data converted to JSON in the controller and then parsed with jQuery.parseJSON() in appointment.js. But this would require increased steps in both the Rails initialization directory, and in your Appointment's model.
So we've covered the scheduler's main components as well as the workflow for creating a new date/hour record and the process of preventing the duplication of a date/hour record.
I packaged this code as a Ruby Gem, which provides a scaffold for you to build a more robust scheduling feature.