Following up on Part 1 and Part 2 of this series where we created a model and rendered a Google calendar-like calendar. Now, finally we’ll finish things out by creating the actions and view that will allow us to view events as well as create and edit new events.
Let’s start with by creating the tip balloon that shows the event title as well as the event time. Here’s what it looks like in Google Calendar:
One thing to keep in mind with this, is that for repeating events we want to show the particular event times for the particular day we clicked on. For example if we have an event that repeats on MWF and starts at the beginning of the month, if we click on the last Friday of the month it should display that particular date. So we need to pass the startTime of that particular occurrence to our show method. So here’s how we modify our calendar.js to do this:
You’ll notice that I used the jQuery plugin qTip2 for our tooltip here. Much like the original qTip it seems this will forever be classified as a release candidate. The plugin, though, is very stable and the constant development and great support from the author are enough for me to use it in a production setting.
So our show action that this javascript uses is pretty simple (just showing different views based on whether or not this is an ajax request or not):
To round out our jQuery plugins we’ll need a modal popup (I’m using the dialog component found in the JQuery UI library), as well as a good datepicker (I’m again using the JQuery UI for this), along with the timePicker add-on to select the specific time of our event. In terms of UI this is a bit different than the way Google Calendar works, but I find Google’s time selection to be a bit clunky. So here’s what our datepicker popup will look like on our startTime and endTime fields:
In order for the dates in this new format to be automatically bound to our Event domain object, I created a custom date registar. First I created a class CustomDateEditorRegistrar.groovy that looks like this:
Then I referenced this new class in the conf/spring/resources.groovy file:
So we have our datepicker setup, now we can use the dialog popup to show and hide the recurring options as needed. Here’s what the recurring options looks like in Google Calendar:
The one thing that makes reproducing this a bit tricky is if we throw the recurring options into a popup, the dialog plugin will pull them out of the form in the DOM. We can handle this by appending the recurring options to the popup when it opens and then putting them back in the form when it closes. That way all our recurring options get posted when we go to save. Here’s what that looks like:
Now, as we look to finish up our controller, we’re not going to be able to use the generated actions to edit or delete and event (again the recurring events make this more complicated). Here’s the prompt that google calendar gives you if you try to edit a recurring event (we get a similar one if we try to delete a recurring event):
So here’s what should actually happen in these three cases when updating a recurring event:
Only this event
Create a new (non-recurring event) with these properties. Add the date of this new event as an exclusion on the original event.
Following events
Create a new recurring event that begins on the selected day. The original event should now end on this day.
All events
Edit the existing event record. No new event record needs to be created.
Here’s what should happen when deleting a recurring event:
Only this event
Add this date as an exclusion on the event.
Following events
Set the recurUntil date to the selected date.
All events
Delete the event record.
In order to keep our Controller lean and to take advantage of the transactions found in services, we’re going to move our update and delete code to our EventService.groovy file:
Now our update and delete controller actions are pretty straightforward:
We’re almost finished now, we just need to add some code to make sure the recurUntil value is set if the user specifies a recurCount value (notice the methods to view the events only look at the recurUntil property):