This tutorial shows how to create a simple Grails web application that displays an AJAX event calendar using the open-source DayPilot Lite for Java library. It supports drag and drop operations (event creating, moving, resizing).
Included
- Full Groovy/Grails and JavaScript source code
- DayPilot Lite for Java 1.0.458
Requirements
- JRE 5 or higher
- Grails 2
- NetBeans (optional)
1. Libraries
JavaScript libraries
- Copy common.js and calendar.js to web-app/js/daypilot directory of your web application.
Java libraries
- Copy daypilot-lite-1.0.458.jar to lib directory of your web application.
2. Grails View
Create a new Grails view (index.gsp) and add references to jQuery and the DayPilot JavaScript libraries to the header or to the body:
<script src="${resource(dir: "js/jquery', file: 'jquery-1.3.2.min.js')}" type="text/javascript"></script> <script src="${resource(dir: "js/daypilot', file: 'common.js')}" type="text/javascript"></script> <script src="${resource(dir: "js/daypilot', file: 'calendar.js')}" type="text/javascript"></script>
Put a placeholder for the Calendar in the <body> :
<div id="dpc"></div>
Add the jQuery plugin initialization code:
<script type="text/javascript"> $(document).ready(function() { var dpc = $("#dpc").daypilotCalendar({ backendUrl : 'backend', viewType : "Week" }); }); </script>
It is necessary to specify the backendUrl property. That's the URL which handles the AJAX callback requests.
3. Grails Controller
Now we need to add a new action to the controller that will handle the backend callback requests.
class CalendarController { static allowedMethods = [backend:'POST'] def index() { } def backend() { new Dpc().process(request, response) } }
The backend action will pass control to a new instance of a Dpc class. Dpc class is a helper class derived from org.daypilot.ui.DayPilotCalendar. It will handle the calendar events, such as eventMove, eventResize, etc.
Now override onInit() method of the DayPilotCalendar class in the Dpc class in order to handle the Init event:
@Override def void onInit() { // set the database fields dataValueField = "event_id"; dataTextField = "event_name"; dataStartField = "event_start"; dataEndField = "event_end"; events = Db.getEvents(request, startDate.toDate(), startDate.addDays(days).toDate()); update(UpdateType.FULL); }
This methods maps the data source fields (dataValueField, dataTextField, dataStartField, dataEndField), loads the event set from a database and requests an update.
org.daypilot.demo.db.Db is a Java class that loads events from a database. In this tutorial, we are using an embedded HSQLDB instance.
public static Table getEvents(HttpServletRequest request, Date from, Date to) throws SQLException, JSONException, ClassNotFoundException { Thread.currentThread().getContextClassLoader().loadClass("org.hsqldb.jdbcDriver" ); Connection c = DriverManager.getConnection(getConnectionString(request), "sa", ""); PreparedStatement st = c.prepareStatement("SELECT event_id, event_name, event_start, event_end FROM EVENTS WHERE NOT ((event_end <= ?) OR (event_start >= ?));"); st.setTimestamp(1, new Timestamp(from.getTime()), Calendar.getInstance(DateTime.UTC)); st.setTimestamp(2, new Timestamp(to.getTime()), Calendar.getInstance(DateTime.UTC)); ResultSet rs = st.executeQuery(); Table table = TableLoader.load(rs); rs.close(); st.close(); c.close(); return table; }
You should replace it with your own data handler.
4. Event Moving
In order to handle event move action, we need to override onEventMove() method. The event arguments are available in EventMoveArgs class (ea parameter).
@Override def void onEventMove(EventMoveArgs ea) { // update the DB Db.moveEvent(request, ea.value, ea.newStart.toTimeStamp(), ea.newEnd.toTimeStamp()); update(); }
It has to be enabled on the client-side as well (eventMoveHandling):
<script type="text/javascript"> $(document).ready(function() { var dpc = $("#dpc").daypilotCalendar({ backendUrl : 'backend', viewType : "Week", eventMoveHandling : "CallBack" }); }); </script>
We have moved the event reloading logic to onFinish() method which is called during every callback after the main event handler:
@Override def void onFinish() { // load the events only if update was requested if (updateType == UpdateType.NONE) { return; } // set the database fields dataValueField = "event_id"; dataTextField = "event_name"; dataStartField = "event_start"; dataEndField = "event_end"; events = Db.getEvents(request, startDate.toDate(), startDate.addDays(days).toDate()); }
5. Event Resizing
Now we will add event resize handler in a similar way.
Override onEventResize method:
@Override def void onEventResize(EventResizeArgs ea) { Db.resizeEvent(request, ea.value, ea.newStart.toTimeStamp(), ea.newEnd.toTimeStamp()); update(); }
And enable it on the client side using eventResizeHandling property:
<script type="text/javascript"> $(document).ready(function() { var dpc = $("#dpc").daypilotCalendar({ backendUrl : 'backend', viewType : "Week", eventMoveHandling : "CallBack", eventResizeHandling: "CallBack" }); }); </script>
6. Event Creating
Selecting a free time range fires timeRangeSelected event. We will handle this event using JavaScript instead of direct CallBack and ask for the event text using a simple JavaScript dialog (prompt):
<script type="text/javascript"> var dpc; $(document).ready(function() { dpc = $("#dpc").daypilotCalendar({ backendUrl : 'backend', viewType : "Week", eventMoveHandling : 'CallBack', eventResizeHandling : 'CallBack', timeRangeSelectedHandling : 'JavaScript', onTimeRangeSelected: function(start, end) { var name = prompt("New Event Name:", "New Event"); dpc.timeRangeSelectedCallBack(start, end, null, { name: name }); dpc.clearSelection(); } }); }); </script>
Server-side handler:
@Override def void onTimeRangeSelected(TimeRangeSelectedArgs ea) { Db.insertEvent(request, ea.data.getString("name"), ea.start.toDate(), ea.end.toDate()); update(); }