Lorem ipsum dolor sit amet Ut purus neque, viverra a, interdum dignissim, nonummy Sed a massa a augue eleifend vulputate Vivamus tortor quam, eleifend
Donec lacinia blandit urna Phasellus sed purus Donec laoreet Vestibulum aliquet lorem ac arcu Donec imperdiet metus sed nunc Sed non elit non elit aliquet viverra CSS Nunc ultrices sodales dui
Suspendisse leo eros, laoreet condimentum, accumsan elementum Vestibulum volutpat arcu ut quam Praesent leo ipsum, sodales a, nulla Sed lacinia varius libero Vivamus tortor felis, rhoncus eget
" body_value = "Lorem ipsum dolor sit amet, consectetuer adipiscing neque, viverra a, interdum dignissim, nonummy Flash eget, nisi Sed eleifend vulputate Vivamus tortor quam, eleifend et, laoreet faucibus, pede Vestibulum risus Nam pede Praesent ac libero Proin arcu " changed = "1184881339" comment = "2" comment_count = "0" created = "1184881274" data = "a:1:{s:7:"contact";i:0;}" field_employment_status = (Array)#2 [0] (Object)#3 value = "Full-time" field_job_role = (Array)#4 [0] (Object)#5 value = "Web Developer/Coder" [1] (Object)#6 value = "RIA Developer" [2] (Object)#7 value = "Video Editor" 344 8962CH13.qxd 11/7/07 10:30 AM Page 345 BUILDING THE JOBS BOARD [3] (Object)#8 value = "Project Manager" field_location = (Array)#9 [0] (Object)#10 value = "On-site" field_location_zip = (Array)#11 [0] (Object)#12 value = "90909" field_pay_range_max = (Array)#13 [0] (Object)#14 value = "200" field_pay_range_min = (Array)#15 [0] (Object)#16 value = "100" field_payment_contract = (Array)#17 [0] (Object)#18 value = "Hourly" field_seniority = (Array)#19 [0] (Object)#20 value = "Mid-Level" field_source = (Array)#21 [0] (Object)#22 value = "From Employer" files = (Array)#23 format = "1" last_comment_name = (null) last_comment_timestamp = "1184881274" log = "" name = "daryl" nid = "40" picture = "" promote = "0" revision_timestamp = "1184881339" status = "1" sticky = "0" taxonomy = (Object)#24 = (Object)#25 description = "Adobe Flash is a super awesome way to make cool things even cooler" name = "Flash" tid = "1" vid = "2" weight = "-10" 10 = (Object)#26 description = "" name = "MXML" tid = "10" vid = "3" weight = "0" 11 = (Object)#27 345 8962CH13.qxd 11/7/07 10:30 AM Page 346 CHAPTER 13 description = "" name = "AS3" tid = "11" vid = "3" weight = "0" 12 = (Object)#28 description = "" name = "CFML" tid = "12" vid = "3" weight = "0" = (Object)#29 description = "Flex Builder, Flex Framework, MXML" name = "Flex" tid = "2" vid = "2" weight = "-9" = (Object)#30 description = "" name = "Dreamweaver" tid = "3" vid = "2" weight = "-7" = (Object)#31 description = "Adobe Integrated Runtime" name = "Adobe Integrated Runtime (AIR)" tid = "4" vid = "2" weight = "-8" teaser = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit viverra a, interdum dignissim, nonummy Flash eget, nisi Sed a massa vulputate Vivamus tortor quam, eleifend et, laoreet faucibus, euismod Vestibulum risus Nam pede Praesent ac libero Proin arcu Morbi lacus Nullam vulputate, augue suscipit mollis pellentesque, ligula pharetra commodo arcu dolor et ante Duis a eros et magna congue." title = "the newest job I swear" type = "job" userid = "1" vid = "65" The record object is pretty long, and the application does not need every bit of data to display the job details required by the specification for the jobs board (which R discussed in Chapter 3) For example, the job details have a required knowledge field, and the values for that display are contained within the taxonomy array in the data object returned by Drupal The completed Job.as DTO looks like this: package com.almerblank.rmx.flex.dto { [Bindable] public class Job extends Object { 346 8962CH14.qxd 11/7/07 11:49 AM Page 373 BUILDING THE EVENT CALENDAR Figure 14-1 Diagram of user filtering for the Events view Figure 14-2 Filtering widget wireframe So, how would you think about this in terms of programming? One way is to think about it in natural language terms For example, the viewing of events is fundamentally a request for data The filters help to shape what the data returned from that request will look like or be composed of In this context, a user possibly wants to find events that meet one or more of the following criteria: Belong to a certain group Are user group meetings, training workshops, Adobe presentations, conferences, etc Are physical events, webcasts, or both Are within x number of miles of the zip code of the specified group Are located in a specified time zone Occur on the selected date 373 8962CH14.qxd 11/7/07 11:49 AM Page 374 CHAPTER 14 If the user wants to see events that span the entire RMX network, the first criteria can be omitted Through the aid of the diagrams, wireframes, and natural language outline, you end up with a pretty clear picture of how you can provide this level of functionality to the user using the tools at your disposal Add that with the technical implementation details that were discussed in Chapter 13, and you have everything necessary to build out this feature custom tailored for events Sharing events For the initial release of the RMX, we chose to offer basic functionality for the sharing of events Each event listed in the directory features an option that allows a user to send an e-mail to a friend letting him know about the event or import the event to the user’s personal calendar like iCal The next phase could possibly include such features as a user-based ratings system that includes both user ratings and comments Creating events For event creation, the goal was to allow user group managers to easily post new events to the community This is something that Flex is really good at, as you’ll discover shortly RMX administrators would need the ability to create both general events that aren’t tied to a specific user group and events specific to a selected user group Figure 14-3 shows the wireframe for this feature Figure 14-3 Event creator wireframe 374 8962CH14.qxd 11/7/07 11:49 AM Page 375 BUILDING THE EVENT CALENDAR Updating events The technical aspect of the data update mechanism was not hard at all as you’ll soon see The only major goal we had for this component was ensuring that group-level administrators have the ability to update events they own while RMX-level administrators have the ability to update any event listed in the network Deleting events Whether there were mistakes in input, an event was canceled, or the data becomes outdated and it’s no longer desirable to maintain it in the system, this feature is necessary for ensuring accurate and meaningful data to the user The same goals that applied to updating also applied to this feature as far as the initial launch was concerned Building the interface Since it’s not discussed in any of the other chapters, let’s talk about the user interface for event creation The interface itself is an MXML file called EventsCreate.mxml It’s pure MXML in that there are no script blocks in that file All of the scripting that needs to take place is handled in the base class for the component, Events.as Here’s the MXML that comprises the event creation form: 376 8962CH14.qxd 11/7/07 11:49 AM Page 377 BUILDING THE EVENT CALENDAR ¯ 378 8962CH14.qxd 11/7/07 11:49 AM Page 379 BUILDING THE EVENT CALENDAR ¯ ¯ There is nothing fancy here as far as the MXML is concerned, other than my use of ASCII syntax in the Start Time and End Time labels It would be nice to use tags instead, but the Flex compiler needs them represented in ASCII format in order to compile the application Other than that, I’m using the same technique mentioned in Chapter 13 where this component’s base class is a custom class that extends one of the built-in container component classes It’s inside this custom class that the variable rmxEvent is declared and defined, and it’s also where all the methods necessary for the use of the form are housed Notice the first namespace declaration, xmlns="com.almerblank.rmx.flex.views admin.components.*"; this is where this component’s base class, Events.as, can be found I’ll show you that class next, but I want to take a minute to talk about how we’re getting the form data from the form Let’s use the component cmb_groups at the bottom of the previous code for an example I’m setting an action to take anytime that component fires its built-in CHANGE event In this case, I want to set the eventGroupId property of the rmxEvent object equal to the data property of the cmb_groups component’s currently selected item As you can see from the previous code, I use the same technique for all the other components except for rbg_eventVenue, where I call the updateVenue() method because I need to take additional actions based on the user’s selection That method, along with cancelEventAction() and createEvent(), are all contained in the base class, so let’s look at that now 379 8962CH14.qxd 11/7/07 11:49 AM Page 380 CHAPTER 14 package com.almerblank.rmx.flex.views.admin.components { import flash.events.*; import import import import import import import mx.collections.ArrayCollection; mx.core.Application; mx.containers.*; mx.controls.*; mx.events.*; mx.effects.easing.Quadratic; mx.formatters.DateFormatter; import com.almerblank.rmx.flex.dto.RmxEvent; import com.almerblank.rmx.flex.events.EventsEvent; import com.almerblank.fl.utils.logging.Logger; import com.almerblank.flex.containers.TitledList; [Bindable] public class Events extends Canvas { public var app:admincontrol; public var rmxEvent:RmxEvent; public var currentDate:Date; public var currentDateStr:String; public var minutes:ArrayCollection; private var dateFormatter:DateFormatter; //references to the components contained in the MXML file public var rbg_eventVenue:RadioButtonGroup; public var tf_connectUrl:TextInput; public var tf_zip:TextInput; public var rbg_Type:RadioButtonGroup; public var cmb_timezone:ComboBox; public var cmb_startMinute:ComboBox; public var cmb_endMinute:ComboBox; public var ns_startHour:NumericStepper; public var ns_endHour:NumericStepper; public var eventsDetail:EventsDetail; public var showEvent:Boolean=false; public var connectEnabled:Boolean = false; public var zipEnabled:Boolean = true; 380 8962CH14.qxd 11/7/07 11:49 AM Page 381 BUILDING THE EVENT CALENDAR public var detailsWidget:Object; public var rsvpWidget:Object; private var _eventId:uint; public var buttonBarDP:Object = ['Edit', 'Delete', 'Hide']; public var rsvpButtonBarDP:Object = ['Cancel', 'RSVP']; public var totalChars:Number = 0; //constructor function public function Events() { super(); app = admincontrol(Application.application); app.addEventListener(EventsEvent.EVENT_SERVICED, _viewEvents); app.addEventListener(EventsEvent.CLEAR_EVENT, _clearEventDetails);¯ rmxEvent = new RmxEvent(); minutes = app.model.minuteOptions; } //create a new event public function createEvent():void { rmxEvent.eventVenue = (rmxEvent.eventVenue == null) ? 'Physical Event' : rmxEvent.eventVenue;¯ rmxEvent.eventType = (rmxEvent.eventType == null) ? 'User Group Meeting' : rmxEvent.eventType;¯ rmxEvent.startMinute = (rmxEvent.startMinute == null) ? '00' : rmxEvent.startMinute;¯ rmxEvent.endMinute = (rmxEvent.endMinute == null) ? '00' : rmxEvent.endMinute;¯ rmxEvent.startTime = String(rmxEvent.startHour) + ':' + rmxEvent.startMinute;¯ rmxEvent.endTime = String(rmxEvent.endHour) + ':' + rmxEvent.endMinute;¯ app.service.createEvent(rmxEvent); } //handle form abandonment public function cancelEventAction():void { rmxEvent = new RmxEvent(); rbg_eventVenue.selectedValue = 'Physical Event'; rbg_Type.selectedValue = 'User Group Meeting'; cmb_timezone.selectedIndex = -1; cmb_startMinute.selectedIndex = 0; cmb_endMinute.selectedIndex = 0; 381 8962CH14.qxd 11/7/07 11:49 AM Page 382 CHAPTER 14 parentDocument.currentState = ''; app.eventsMgr.viewEvents.eventsList eventsList.tileList.selectedIndex = -1¯ } //perform additional steps based on user's venue selection public function updateVenue(event:Event):void { var venue:String = String(event.target.selection.value); rmxEvent.eventVenue = venue; switch(venue) { case 'Physical Event': tf_connectUrl.enabled = false; tf_zip.enabled = true; break; case 'Connect Session': tf_connectUrl.enabled = true; tf_zip.enabled = false; break; case 'Both': tf_connectUrl.enabled = true; tf_zip.enabled = true; break; } } //reset the form every time a new event is created private function _clearEventDetails(event:EventsEvent):void { rmxEvent = new RmxEvent(null); Logger.log(rmxEvent); if(detailsWidget != null) { detailsWidget.visible = false; } else { showEvent = false; } totalChars = 0; } //return to the event browser private function _viewEvents(events:EventsEvent):void { app.eventsMgr.currentState = ''; app.eventsMgr.viewEvents.eventsList eventsList.tileList.selectedIndex = -1¯ 382 8962CH14.qxd 11/7/07 11:49 AM Page 383 BUILDING THE EVENT CALENDAR } } } First thing that I is to import any classes or packages that I need from the Flex framework After that, I import any of the custom classes that I need RmxEvent is the data transfer object (DTO), EventsEvent is the custom event class, Logger is a custom class we use at Almer/Blank internally for allowing runtime debugging both locally and remotely, and finally TitledList is a custom container that’s actually used in the EventsView component, which is part of the events browser mechanism The next important section is the variable declarations section where I include, among other things, references to the components contained in the MXML file The other variables important to event creation are app, rmxEvent, minutes, connectEnabled, zipEnabled, and totalChars The app variable is my shortcut to the main MXML file As detailed in Chapter 13, this gives me code hinting and error checking as I work out my custom visions, and that’s seriously cool rmxEvent is the object that you saw referenced in the form and it’s of type RmxEvent, so that variable lets us access the DTO and update its properties without a whole lot of effort I’ll show you the DTO shortly, but once again, this is a timesaving concept Speaking of time, the minutes variable is the dataProvider for the ComboBox components cmb_startMinute and cmb_endMinute The totalChars variable is the numeric display attached to the TextArea component, ta_eventDesc, which indicates how many characters have been typed in the text field That takes care of the variables section, so let’s step into the guts of the class This class extends Canvas, so the first thing we in the constructor is call the constructor of the super class so that we have access to all its public properties and methods After that, we define app as an instance of Application.application type-cast to admincontrol, which is the name of the main MXML file That step is what gives us the code hints and error checking Next, we add a couple of event listeners to app for the EVENT_SERVICED and CLEAR_EVENT events EVENT_SERVICED is dispatched after a successful remoting call, in this instance creating an event, while the Post Event button found on the events browser page dispatches CLEAR_EVENT when it’s clicked Its purpose is to ensure that the form is reset every time a new event is to be created The methods for handling event creation are quite simple actually In the first four lines of createEvent(), we first check to see if those properties of the DTO still have their default values of null If they do, then that means that no user selection was made (the user chose to stick with the default component selections) If that’s the case, we need to update those properties of the DTO After that’s done, we need to create the values for the startTime and endTime properties of the DTO, which are a combination of the values of the startHour, startMinute, endHour, and endMinute DTO properties Once all of that’s complete, we can call the remote service createEvent(), passing our DTO as the only argument The cancelEventAction() method is similar to createEvent(), only here we want to empty the DTO, set the radio button groups, check boxes, and combo boxes back to their default values, and we want to return the user to the events browser The updateVenue() method is called when a user changes the selection for the Venue radio button group In this method, we just need to enable or disable the Connect URL and Zip fields based on the user’s venue selection and then update the eventVenue property of the DTO with that selection The _clearEventDetails() method is the event handler for that CLEAR_EVENT mentioned earlier It does all the work as far as resetting the DTO The conditional statement only has relevance to the events detail component The _viewEvents() method is the event handler for the EVENT_SERVICED event that was mentioned earlier, and it’s responsible for returning 383 8962CH14.qxd 11/7/07 11:49 AM Page 384 CHAPTER 14 the user to the events browser after a successful event posting That covers all of the mechanics behind the event creation form, so let’s look at the DTO to see how the object that we’re sending to the server is actually constructed Have data, will travel In my humble opinion, data transfer objects are the coolest thing since sliced bread when it comes to data exchange in the Flex universe They just make your development life a lot easier when utilized properly Our DTO for the events section of the RMX is very similar to the one described in Chapter 13 for jobs Here’s the code for it: package com.almerblank.rmx.flex.dto { import mx.utils.ObjectUtil; import com.almerblank.fl.utils.logging.Logger; [Bindable] public class RmxEvent extends Object { //default data properties public var eventId:uint; public var eventVenue:String = 'Physical Event'; public var eventName:String; public var rsvpPhysical:Boolean = false; public var rsvpPhysicalEmail:String; public var rsvpConnect:Boolean = false; public var rsvpConnectEmail:String; public var eventType:String = 'User Group Meeting'; public var timezone:String; public var startDate:String = ''; public var endDate:String = ''; public var startTime:String; public var endTime:String; public var eventDesc:String; public var eventGroupId:uint; public var eventZip:uint; public var eventUrl:String; public var eventGroupName:String; public var eventLocation:String; //custom external property (php only!!!) //custom internal properties (flex only!!!) public var startHour:Number = 15; public var startMinute:String; public var endHour:Number = 17; public var endMinute:String; public var otherType:String; 384 8962CH14.qxd 11/7/07 11:49 AM Page 385 BUILDING THE EVENT CALENDAR public function RmxEvent(newInfo:Object=null) { super(); if(newInfo) _parseObject(newInfo); } private function _parseObject(newInfo:Object):void { try { for(var i:String in newInfo) { if(this.hasOwnProperty(i)) this[i] = newInfo[i]; } //process our internal vars var eventStartTime:Array = startTime.split(':'); startHour = eventStartTime[0]; startMinute = eventStartTime[1]; var eventEndTime:Array = endTime.split(':'); endHour = eventEndTime[0]; endMinute = eventEndTime[1]; Logger.log('new event data = ' + ObjectUtil.toString(this), this);¯ } catch(error:*) { Logger.log(ObjectUtil.toString(error), this); } } } } When declaring DTO properties, I always organize them into categories so that I have a better understanding of their purposes Here there are three main categories: default, PHP-only, and Flex-only The default properties are ones that exist in the database These are the ones that will be returned by a SQL query The PHP-only variables can be anything that you might want to use once the data makes it to the server but that you don’t necessarily need or want to include in your database updates The internal variables are those that derive their value from one or more of the default values For example, in this DTO I set the values for endHour and endMinute after splitting endTime into an array So, for incoming or outgoing data operations, you’ll always have nice little code hints that tell you what’s available to be read (viewing) or modified (creating/updating) 385 8962CH14.qxd 11/7/07 11:49 AM Page 386 CHAPTER 14 Back-end integration As you’ve probably seen for yourself in the previous chapter, interfacing with Drupal is very cool It definitely saved us a bunch of time from developing a complete back-end system from scratch Out of the box, and with minimal tweaking, we were able to build a system to satisfy the seemingly insatiable appetite of the RMX for complex data In Chapter 13, you discovered how to set up your development and production environments for Flex/Drupal integration, as well as how to pull in the job listings for viewing via the jobs browser Here, we’ll explore how the events created via the Flex form are dissected on the Drupal side We took some time and effort to configure the service returns and server-side object parsing so that they didn’t add any additional overhead to the Flex code In other words, I get to receive my data and send my DTO in the formats described earlier, and in the case of a create or update event, that DTO is parsed on the server and mapped to the proper fields for insertion into the Drupal database In this way, I don’t have to know how Drupal labels these various pieces of data As a Flex developer, this is heaven, because Drupal does underscores and all-lowercase for your custom field names when you create custom nodes, and that’s no fun to read or work with especially when you have a lot of data to process Now, since we’ve covered custom module creation in the previous chapter, I’ll just focus on the service method of the custom events module that lets us write new data to the database, events.create We need to the same things for this method as we did for the jobs view: we need to define the method in the “method table” and create the callback for the method Here’s the code needed for the method definition: array ( '#method'=>'events.create', '#callback'=>'events_service_create', '#return'=>'struct', '#args'=>array ( array ( '#name'=>'event', '#type'=>'struct', '#description'=>t('An event object to send to the service Include "type" for creation.'),¯ ) ), '#help'=>t('Creates a new event.') ), Here we define the method as events.create and then we define the callback method, or the method that’s going to all the real work, as events_service_create We set the return type to struct, but when the data comes back to Flex, it’s going to be an array of objects You might want to think of it this way at all times, because unless you’re from a Java or C background, you might not have ever heard of structs before And, in case you haven’t heard of this term before, a struct is just a data type that has one or more members, each of which can have different types, for example, “Object.” After setting the return type, we define what kind of argument we expect passed in to the method And finally, we define the help text that’s displayed in the Drupal service browser 386 8962CH14.qxd 11/7/07 11:49 AM Page 387 BUILDING THE EVENT CALENDAR Our callback method is constructed very similarly to how we’d perform this kind of operation in a traditional AMFPHP install The major difference is we have to rely on Drupal “hooks” to ensure operational success These are basically intrinsic API methods that let you “hook” your custom code into the Drupal core Let’s look at the code for events_service_create: function events_service_create($arr) { foreach($arr[0] as $key=>$value) { $$key = $value; } node_validate($arr); if($errors = form_get_errors()) { return services_error(implode("\n", $errors)); } $node = node_submit($arr); //some default values you may want to populate $node->uid = 1; $node->type = 'rmx_event'; $node->title = $eventName; $node->body = $eventDesc; $node->created = time(); $node->teaser = $node->body; //fill in the values necessary for Drupal's event module $node->event_start = strtotime($startDate); $node->event_end = strtotime($endDate); $node->timezone = 487; //fill your custom fields $node->field_rsvpphysical_value = $rsvpPhysical; $node->field_rsvpphysicalemail_value = $rsvpPhysicalEmail; $node->field_rsvpconnect_value = $rsvpConnect; $node->field_rsvpconnectemail_value = $rsvpConnectEmail; $node->field_eventtype_value = $eventType; $node->field_timezone_value = $timezone; $node->field_starttime_value = date('H:i', $node->event_start); $node->field_endtime_value = date('H:i', $node->event_end); $node->field_eventgroupid_value = $eventGroupId; $node->field_eventzip_value = $eventZip; $node->field_eventurl_value = $eventUrl; $node->field_eventvenue_value = $eventVenue; node_save($node); 387 ... totalChars=ta_eventDesc.text.length"/>¯