CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 281 "deserialize" : function(str){ }, "urldecode" : function(str) { } }; Fixing Timezone Inconsistencies You aren’t passing a time or timezone to the Date object, so that object will default to midnight Greenwich Mean Time (00:00:00 GMT). This can cause your dates to behave unexpectedly for users in different timezones. To address this problem, you’ll need to adjust the date by the timezone offset using two built-in Date object methods: .setMinutes() and .getTimezoneOffset(). The return value of .getTimezoneOffset() is the difference in the number of minutes between GMT and the user’s timezone. For instance, the return value of .getTimezoneOffset() in Mountain Standard Time (-0700) is 420. Using .setMinutes(), you can add the value of the timezone offset to the Date object, which will return the date to midnight on the given day, no matter what timezone the user is in. You can make that adjustment using the following bold code: fx = { "initModal" : function() { }, "boxin" : function(data, modal) { }, "boxout" : function(event) { }, // Adds a new event to the markup after saving "addevent" : function(data, formData){ // Converts the query string to an object var entry = fx.deserialize(formData), // Makes a date object for current month cal = new Date(NaN), // Makes a date object for the new event event = new Date(NaN), // Extracts the event day, month, and year date = entry.event_start.split(' ')[0], // Splits the event data into pieces edata = date.split('-'), // Extracts the calendar month from the H2 ID cdata = $("h2").attr("id").split('-'); // Sets the date for the calendar date object cal.setFullYear(cdata[1], cdata[2], 1); // Sets the date for the event date object event.setFullYear(edata[0], edata[1], edata[2]); // Since the date object is created using CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 282 // GMT, then adjusted for the local timezone, // adjust the offset to ensure a proper date event.setMinutes(event.getTimezoneOffset()); }, "deserialize" : function(str){ }, "urldecode" : function(str) { } }; Ensuring the Event Occurs in the Current Month Your next step is to set up a conditional statement that ensures that only events that belong on the calendar are appended. If both the year and month match between the current calendar month and the event date, you can extract the day of the month using the Date object’s .getDay() method. To work properly with the next step, which adds leading zeroes to single-digit dates, you also need to convert this value to a string, which is accomplished by passing the value to String(). The day of the month needs to have a leading zero to properly match the calendar. For example, if the returned date is only one digit, you prepend a leading zero to the date. Do this by inserting the following bold code: fx = { "initModal" : function() { }, "boxin" : function(data, modal) { }, "boxout" : function(event) { }, // Adds a new event to the markup after saving "addevent" : function(data, formData){ // Converts the query string to an object var entry = fx.deserialize(formData), // Makes a date object for current month cal = new Date(NaN), // Makes a date object for the new event event = new Date(NaN), // Extracts the event day, month, and year date = entry.event_start.split(' ')[0], // Splits the event data into pieces edata = date.split('-'), // Extracts the calendar month from the H2 ID cdata = $("h2").attr("id").split('-'); // Sets the date for the calendar date object cal.setFullYear(cdata[1], cdata[2], 1); // Sets the date for the event date object event.setFullYear(edata[0], edata[1], edata[2]); CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 283 // Since the date object is created using // GMT, then adjusted for the local timezone, // adjust the offset to ensure a proper date event.setMinutes(event.getTimezoneOffset()); // If the year and month match, start the process // of adding the new event to the calendar if ( cal.getFullYear()==event.getFullYear() && cal.getMonth()==event.getMonth() ) { // Gets the day of the month for event var day = String(event.getDate()); // Adds a leading zero to 1-digit days day = day.length==1 ? "0"+day : day; } }, "deserialize" : function(str){ }, "urldecode" : function(str) { } }; Appending the Event to the Calendar You’re finally ready to append the new event to the calendar. To do so, create a new anchor element, hide it, set its href attribute, and use the title of the event as the link text. Next, set a one-second delay using .delay(1000) and fade in the new event. You can implement this by adding the following code shown in bold: fx = { "initModal" : function() { }, "boxin" : function(data, modal) { }, "boxout" : function(event) { }, // Adds a new event to the markup after saving "addevent" : function(data, formData){ // Converts the query string to an object var entry = fx.deserialize(formData), // Makes a date object for current month cal = new Date(NaN), // Makes a date object for the new event event = new Date(NaN), // Extracts the event day, month, and year date = entry.event_start.split(' ')[0], // Splits the event data into pieces CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 284 edata = date.split('-'), // Extracts the calendar month from the H2 ID cdata = $("h2").attr("id").split('-'); // Sets the date for the calendar date object cal.setFullYear(cdata[1], cdata[2], 1); // Sets the date for the event date object event.setFullYear(edata[0], edata[1], edata[2]); // Since the date object is created using // GMT, then adjusted for the local timezone, // adjust the offset to ensure a proper date event.setMinutes(event.getTimezoneOffset()); // If the year and month match, start the process // of adding the new event to the calendar if ( cal.getFullYear()==event.getFullYear() && cal.getMonth()==event.getMonth() ) { // Gets the day of the month for event var day = String(event.getDate()); // Adds a leading zero to 1-digit days day = day.length==1 ? "0"+day : day; // Adds the new date link $("<a>") .hide() .attr("href", "view.php?event_id="+data) .text(entry.event_title) .insertAfter($("strong:contains("+day+")")) .delay(1000) .fadeIn("slow"); } }, "deserialize" : function(str){ }, "urldecode" : function(str) { } } ■ Note The data variable is undefined as of right now. You’ll remedy this in the next section. Now, back in the click event handler for the Submit button, modify the success callback of the $.ajax() function to execute fx.addevent() by using the following bold code: CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 285 // Edits events without reloading $(".edit-form input[type=submit]").live("click", function(event){ // Prevents the default form action from executing event.preventDefault(); // Serializes the form data for use with $.ajax() var formData = $(this).parents("form").serialize(); // Sends the data to the processing file $.ajax({ type: "POST", url: processFile, data: formData, success: function(data) { // Fades out the modal window fx.boxout(); // Adds the event to the calendar fx.addevent(data, formData); }, error: function(msg) { alert(msg); } }); }); Save this file and reload http://localhost/. Bring up the event creation form and create a new event with the following information: • Event Title: Addition Test • Event Start: 2010-01-09 12:00:00 • Event End: 2010-01-09 14:00:00 • Event Description: This is a test of the dynamic addition of new events to the calendar. Submitting the form causes the modal window to fade out; a second later, the new event title will fade in on the calendar in the proper place (see Figure 8-2). CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 286 Figure 8-2. The calendar after the new event is created Getting the New Event’s ID Currently, a new event is not viewable without a page refresh after it is created. Because the event ID is absent (nothing is returned from a successful event addition), clicking the generated link results in an empty modal window (see Figure 8-3). CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 287 Figure 8-3. Here the event cannot be loaded because no event ID is available Modifying the Event Creation Method to Return New Event IDs To make the event immediately viewable, you only need to make one small adjustment in the Calendar class. Open the file (/sys/class/class.calendar.inc.php) and locate the processForm() method. Inside this method, modify the return command to output the ID of the last inserted row using PDO’s lastInsertId() method: public function processForm() { /* * Exit if the action isn't set properly */ if ( $_POST['action']!='event_edit' ) { return "The method processForm was accessed incorrectly"; } CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 288 /* * Escape data from the form */ $title = htmlentities($_POST['event_title'], ENT_QUOTES); $desc = htmlentities($_POST['event_description'], ENT_QUOTES); $start = htmlentities($_POST['event_start'], ENT_QUOTES); $end = htmlentities($_POST['event_end'], ENT_QUOTES); /* * If no event ID passed, create a new event */ if ( empty($_POST['event_id']) ) { $sql = "INSERT INTO `events` (`event_title`, `event_desc`, `event_start`, `event_end`) VALUES (:title, :description, :start, :end)"; } /* * Update the event if it's being edited */ else { /* * Cast the event ID as an integer for security */ $id = (int) $_POST['event_id']; $sql = "UPDATE `events` SET `event_title`=:title, `event_desc`=:description, `event_start`=:start, `event_end`=:end WHERE `event_id`=$id"; } /* * Execute the create or edit query after binding the data */ try { $stmt = $this->db->prepare($sql); $stmt->bindParam(":title", $title, PDO::PARAM_STR); $stmt->bindParam(":description", $desc, PDO::PARAM_STR); $stmt->bindParam(":start", $start, PDO::PARAM_STR); $stmt->bindParam(":end", $end, PDO::PARAM_STR); $stmt->execute(); $stmt->closeCursor(); /* CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 289 * Returns the ID of the event */ return $this->db->lastInsertId(); } catch ( Exception $e ) { return $e->getMessage(); } } After making the preceding change, save this file and reload http://localhost/ in your browser. Next, create a new event with the following information: • Event Title: ID Test • Event Start: 2010-01-06 12:00:00 • Event End: 2010-01-06 16:00:00 • Event Description: This event should be immediately viewable after creation. Now save the event, and the title will appear on the calendar. Click the title, and the event will load in a modal window (see Figure 8-4). CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 290 Figure 8-4. An event loaded immediately after creation Editing Events in a Modal Window In its current state, your app is only a short ways away from allowing users to edit events from the modal window, as well. The existing click event handler for loading the event creation form will also work for event editing with only a little modification. To start, expand the selector to include any element with a class admin; you can accomplish this by including the following bold code: // Displays the edit form as a modal window $(".admin-options form,.admin").live("click", function(event){ // Prevents the form from submitting event.preventDefault(); // Loads the action for the processing file . $stmt = $this->db->prepare($sql); $stmt->bindParam(":title", $title, PDO::PARAM_STR); $stmt->bindParam(":description", $desc, PDO::PARAM_STR); $stmt->bindParam(":start",. the following information: • Event Title: Addition Test • Event Start: 201 0-0 1-0 9 12:00:00 • Event End: 201 0-0 1-0 9 14:00:00 • Event Description: This is a test of the dynamic addition of. PDO::PARAM_STR); $stmt->bindParam(":end", $end, PDO::PARAM_STR); $stmt->execute(); $stmt->closeCursor(); /* CHAPTER 8 ■ EDITING THE CALENDAR WITH AJAX AND JQUERY 289