One of the more interesting features of Dreamweaver MX 2004 is the capability to create an ActionScript document that controls a Flash movie. In the case of our booking facility, the code will not only control the various buttons and so on in the Flash movie, it will also be used to add data to the MySQL database and pull information from the database that is displayed in the Flash movie.
The main advantage to this approach, as opposed to having the code embedded in the Flash movie, is the ability to make changes as needed. When a Flash movie is posted to a web site, it is in the form of a non-editable file known as an .swf. If something is not working and you need access to the code, a .swf file essentially prevents access to that code. Unless you have the .fla file in your possession or it is readily accessible, you will have a huge problem on your hands. Keeping the code separate enables the coder to focus on the code without the need of the .fla file, enabling the project to be split up. The final movie can then be compiled with all the parts together. The other really neat thing about this technique is that the script only requires one line of code in the Flash movie in order to execute.
Finally, this code uses ActionScript 2, the latest update to the Flash programming language. It is based on the ECMAScript 4 and the JavaScript 2.0 language proposals. It provides a structured approach to object-oriented programming with the syntactic formalization of classes, inheritance, type-checking, private/public members, and static members. Though that may seem like a rather formal description, this version of the language adheres very closely to the ECMA syntax.
Scripts that use ActionScript 2 language elements, such as the one that follows, must be stored as external script files using the .as extension, with a single class defined in each script. In addition, other than the import statement, ActionScript 2 terminology is not supported in the Actions panel.
Follow these steps to create an ActionScript document in Dreamweaver MX 2004:
Open Dreamweaver and make sure you are working with the Oakbridge site.
Select File, New to open the New Document dialog box.
Select Other, ActionScript (see Figure 13.4) and click OK. A blank ActionScript document will open.
Note
The following code has been fully commented to explain the purpose of each section of the code. If this is far too much typing for you, the file is available for download from the book's web site. It is named "booking.as." This file has to be placed in the same directory as your booking.fla file for compiling purposes.
The first thing you need to do is import the NetServices, DataGlue, and NetDebug ActionScript classes used by Flash Remoting into the movie. Enter the following code in the ActionScript Document.
// ActionScript Document #include "NetDebug.as" #include "NetServices.as" #include "DataGlue.as"
NetDug allows for support of Flash's NetConnection Debugger. When you deploy your Flash movie, be sure either to comment the line (//) or remove the line totally. It only adds extra weight. It also makes your movie accessible to external debugging, which may threaten the security of your application. The best practice is to comment it out because if you ever need to debug your code again, it is faster to remove the comment.
NetServices provides prebuilt objects that automate the connection process.
DataGlue enables you to populate the Flash UI Components with Recordsets. In our case, we will be using the ComboBox and Dynamic Text Box components when we actually assemble the Flash movie.
The next thing we need to do is tell Flash where the web server is located and where the buttons to control the movie will be placed on the Flash stage. The location you enter would be either the location of the site's server or the address for the localhost on your machine. Press the Return/Enter key and enter the following code:
// ------------------------- USER CHANGEABLE AREA ---------------- // change the URL to match YOUR web server - this one matches MINE ourURL = "http://bear-faced-cow.local:8080/cfusion/flashservices/gateway"; timeSlot_x = 100; timeSlot_y = 53; // ------------------------- END USER CHANGEABLE AREA ------------
It is also important to remember the /flashservices/gateway part of the URL. Although it looks like a series of folders, it is actually calling a servlet that connects Flash to ColdFusion. So, don't go looking for them on your system.
The next bit of ActionScript tells the buttons what they are supposed to do. In a way, we are doing things a little backward here because we are creating an array of objects (called bookingBtn) and initializing them before we have defined the object. This type of programming is often known as a top-down approach (where defining the object first and then using it would be considered a bottom-up approach). Neither way is more correct than the other. It's more or less what you are used to. Some programmers need to see the big picture first and then fill in the little details. Enter the following code:
[View full width]tomorrowBtn.onRelease = tomorrow; // set our two date changing buttons to that when they get hit yesterdayBtn.onRelease = yesterday; // they will call these functions bookingButtons = new Array(); for( loop = 0; loop < 10; loop++ ) { _root.attachMovie( "Rectangle Button", "booking" + loop, 100 + loop ); bookingButtons[loop] = new bookingBtn( 9 + loop, _root["booking" + loop] ); _root["booking" + loop].onRelease = _root["btnPressed" + loop]; // point our handler to our button handling functions _root["booking" + loop]._x = timeSlot_x; _root["booking" + loop]._y = (loop * parseInt(_root["booking" + loop]._height)) + timeSlot_y; // calculate our y in our movie }
The purpose of a function in Flash is to give you the ability to create modular code that can be reused. The following two functions determine the dates for yesterday and tomorrow. Enter the following code:
[View full width]// ------------------------- FUNCTIONS --------------------------- function tomorrow() { _root.theDate.setTime( _root.theDate.getTime() + (24 * 60 * 60 * 1000) ); _root.theDate = new Date( _root.theDate.getFullYear(), _root.theDate.getMonth(), _root.theDate.getDate(), 0, 0, 0, 0 ); // and remove the time to set it to 12:00am by default setOurDate( _root.theDate ); _root.facilities.getBooking( facilitiesList.getSelectedItem() .data, _root.theDate .getTime() / 1000); } function yesterday() { _root.theDate.setTime( _root.theDate.getTime() - (24 * 60 * 60 * 1000) ); _root.theDate = new Date( _root.theDate.getFullYear(), _root.theDate.getMonth(), _root.theDate.getDate(), 0, 0, 0, 0 ); // and remove the time to set it to 12:00am by default setOurDate( _root.theDate ); _root.facilities.getBooking( facilitiesList.getSelectedItem() .data, _root.theDate .getTime() / 1000); }
Having dealt with the way dates are handled, you can now decide what happens when buttons are pressed. Enter the following code:
function btnPressed0() { _root.bookingButtons[0].btnPressed(); } function btnPressed1() { _root.bookingButtons[1].btnPressed(); } function btnPressed2() { _root.bookingButtons[2].btnPressed(); } function btnPressed3() { _root.bookingButtons[3].btnPressed(); } function btnPressed4() { _root.bookingButtons[4].btnPressed(); } function btnPressed5() { _root.bookingButtons[5].btnPressed(); } function btnPressed6() { _root.bookingButtons[6].btnPressed(); } function btnPressed7() { _root.bookingButtons[7].btnPressed(); } function btnPressed8() { _root.bookingButtons[8].btnPressed(); } function btnPressed9() { _root.bookingButtons[9].btnPressed(); }
Although there are more efficient ways of coding this, the code was written here for the sole purpose of clarity.
The next function adds the date to a dynamic text box on the stage. Enter the following code:
[View full width]function setOurDate( aDate ) { var monthArray = new Array( "Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec." ); // put our date in our text box _root.dateDisp.text = monthArray[aDate.getMonth()] + " " + aDate.getDate() + ", " + aDate.getFullYear(); }
The ComboBox will contain a pop-down list of the Oakbridge facilities drawn from the MySQL database. The following code will be called when we select a new list item in our ComboBox component:
// because components now broadcast their actions using the event model // we need to define a makeshift object to receive our event changes // and process the event in order to get a new booking facilityObj = new Object(); facilityObj.change = function () { var ourSelection = _root.facilitiesList.getSelectedItem() // trace( "Facility Chosen: " + ourSelection.facility ); // call our CFMX web service with our data from our comboBox along with a UNIX timeStamp of our date without the milliseconds for( loop = 0; loop < 10; loop++ ) { _root.bookingButtons[loop].setBooking( 0 ); // init our state } _root.facilities.getBooking( ourSelection.data, _root.theDate.getTime() / 1000 ); } // in order to receive the changes from the ComboBox, we need to add // our object as a listener of the ComboBox. This way, our object is // automatically called when the user // makes a new selection in our combo box. _root.facilitiesList.addEventListener( "change", facilityObj );
Aside from having to tell Flash Remoting what information should be pulled from or added to the MySQL database, the following functions are called automatically by our Flash Remoting calls with the information that we requested. Enter the following:
[View full width]// --------------------- FLASH REMOTING METHODS ---------------------- // these functions are called automatically from our web service call function listFacilities_Result( result ) { _root.facilitiesList.dataProvider = result; // binding doesn't automatically send a change event, so we will // get our selected item manually _root.facilities.getBooking( _root.facilitiesList.getSelectedItem().data, _root .theDate.getTime() / 1000 ); } function addBooking_Result( result ) { trace( "Create Booking: " + result ); _root.facilities.getBooking( facilitiesList.getSelectedItem() .data, _root.theDate .getTime() / 1000); } function removeBooking_Result( result ) { trace( "Remove Booking: " + result ); _root.facilities.getBooking( facilitiesList.getSelectedItem() .data, _root.theDate .getTime()/ 1000 ); } function getBooking_Result( result ) { trace( "Received Bookings for " + _root.theDate ); for( loop = 0; loop < 10; loop++ ) { var aTimeStamp = ( _root.theDate.getTime() / 1000 ) + ( ( loop + 9 ) * 3600 ) ; // get our timestamp _root.bookingButtons[loop].setBooking( 0 ); // init our state // iterate through our results that were returned from ColdFusion MX for( resultLoop = 0; resultLoop < result.items.length; resultLoop++ ) { row = result.items[resultLoop]; trace( "Booking ID: " + row.bookingID + " - Start: " + row.startDate + " (" + row.bookingStart + ") - Duration: " + row.duration ); if( aTimeStamp >= row.bookingStart && aTimeStamp <= (row.bookingStart + row.duration) ) { _root.bookingButtons[loop].setBooking( row.bookingID ); // set our button } } } }
Note
One of our tech editors, Marcus Dickinson, chimed in with another method of error catching. His code is somewhat similar to ours. The reason we are including it is to make the point that coders all have their "styles." The interesting thing about this is they all end up in the same place.
[View full width]function removeBooking_Error( error ) { errormessage.text = error.description; // This is pulled automatically from CFMX Server. Very useful when NetConnectionDebugger is not running live. For localhost, it is just another way of checking. }
The following code defines an object to which each button is attached. Our bookingBtn object defines a blueprint as to what happens when a button requesting a booking is pressed. This involves starting the check and creating or removing a reservation. You also note Jordan has added his name to the code. This is a normal procedure among coders, and it identifies that the following code is a custom code object he wrote. Enter the following:
[View full width]// --------------------- bookingBtn OBJECT ---------------------------- // by: Jordan L. Chilcott, Interactivity Unlimited // This will handle the various booking buttons that are displayed // and handled within the timesheet function bookingBtn( timeSlot, btn ) { this.hour = timeSlot; this.btn = btn; // used to control our button this.bookingID = 0; // initialize our booking ID here } bookingBtn.prototype.createBooking = function () { aDate = new Date( _root.theDate.getFullYear(), _root.theDate.getMonth(), _root .theDate.getDate(), this.hour, 0, 0, 0 ); // create a date object of the time we want to book trace( "Sending booking msg: " + _root.facilitiesList.getSelectedItem().label + "," + aDate + "," + 3599 ); facilities.addBooking( _root.facilitiesList.getSelectedItem() .data, aDate.getTime() / 1000, 3599 ); // book an hour at the allotted timeslot } bookingBtn.prototype.removeBooking = function () { _root.facilities.removeBooking( this.bookingID ); } bookingBtn.prototype.setBooking = function ( bookingID ) { trace( "Setting booking ID to: " + bookingID ); this.bookingID = bookingID; this.btn._alpha = this.bookingID ? 30 : 0; // if we have a booking, shade the button } // this is our public button handler bookingBtn.prototype.btnPressed = function () { trace( "Button Pressed!"); if( this.bookingID ) { this.removeBooking(); } else { this.createBooking(); } } // --------------------------------------------------------------------
The next bit of code puts today's date into its location in the movie. It is an actual part of the initialization routines for our movie. Enter the following:
_root.theDate = new Date(); // start off our date with today _root.theDate = new Date( _root.theDate.getFullYear(), _root.theDate.getMonth(), _root.theDate.getDate(), 0, 0, 0, 0 ); // and remove the time to set it to 12:00am by default setOurDate( _root.theDate );
The final bit of code checks to see that the Flash Remoting gateway is open through a flag that we have set. If our flag isn't set, we proceed to open up a Flash Remoting gateway and call up our ColdFusion Component (called "facilities"). Note the dot syntax. This is how directories are notated in Java. Enter the following:
// connect to the Flash Remoting service provider if (isGatewayOpen == null) { // do this code only once isGatewayOpen = true; // Make the Gateway connection NetServices.setDefaultGatewayUrl(ourURL); gatewayConnnection = NetServices.createGatewayConnection(); // Connect to our web service (aka: facilities.cfc) _root.facilities = gatewayConnnection.getService("oakbridges. facilities", this); // Call our Web service: get our list of facilities for our dropdown list _root.facilities.listFacilities(); }
Save the document as booking.as. Be sure to save it to the same folder in which your booking.fla file is located.
The ActionScript does all the work on the Flash side. ColdFusion MX provides the back-end support. Clicking a button in the Flash movie will trigger a call to access the MySQL database. That call has to move through ColdFusion and is managed by a ColdFusion Component. This component is usually a separate code file with the .cfc extension.
Components were introduced in ColdFusion, and their greatest strength is that they promote code reuse. This makes application development, such the booking facility, a less complex process. For the hard-core coders, components offer an OOP (Object Oriented programming) approach to web application development. The CFC framework is based on the object-oriented concepts of classes, methods, instances, and properties. A full explanation of this is well out of the scope of this book. If you are looking for a general idea of how ColdFusion works, David Golden's New Riders book, ColdFusion MX with Dreamweaver MX is a good starting point. If you are hard-core, then Ben Forta and others' Macromedia Press book, ColdFusion MX Web Application Construction Kit, Fifth Edition, is the most complete and comprehensive book on the street (at 1500 pages, it better be comprehensive).
ColdFusion MX also includes the Flash Remoting service, which acts as the gateway for communication between your Flash movie and ColdFusion. The Flash Remoting services, along with the ColdFusion Components, act as an extension to Flash ActionScript because they extend your Flash movie's capabilities to anything from a simple emailer to a booking application. All you really need to know is that Flash Remoting provides the connection between Flash MX 2004 and ColdFusion MX, and you don't need to do any configuration to make it work. From our perspective, it just works.
Follow these steps to create a ColdFusion Component in Dreamweaver:
Open Dreamweaver and ensure you are working in the Oakbridge site.
Select File, New to open the New Document dialog box shown in Figure 13.5.
Select the Dynamic Page category and select ColdFusion Component from the list in the Dynamic Page area. Click OK, and a page with code already entered will open.
The code on the page is actually the basic ColdFusion MX code for the creation of a component:
<cfcomponent> <cffunction name="myFunction" access=" public" returntype="string"> <cfargument name="myArgument" type="string" required="true"> <cfset myResult="foo"> <cfreturn myResult> </cffunction> </cfcomponent>
The <cfcomponent> statement defines the component and the <cffunction> statement is enclosed in the component definition. When components are used, they call functions, and you can have multiple functions between the <cfcomponent> tags. Using a <cffunction> in ColdFusion is theoretically no different from using a function in ActionScript. They accept arguments, return values, and can be reused as often as you want.
Functions typically contain the three attributes shown. They are:
Name: Names the function; is used when you invoke a component method.
Access: Specifies the client that can call a component method and uses one of four attributes. The first attribute is "remote," which is accessible by the FORM and GET methods and is used by the Flash Remoting service. The second is "private," which means the component can only call on itself. The third is "package," which restricts the calls to only the files in the site's web root. The fourth, "public," opens the call to HTTP methods and other means.
returnType: Identifies the type of data the component returns from the database. This could be strings, arrays, numbers, and so on.
The parameters for the function are defined in the <cfargument> tag. The required attribute enables you to set an argument as being required by the function. Finally, the <cfreturn> tag presents the results of whatever CFML processing is done within the function. You can only use one <cfreturn> tag per <cffunction> statement.
Now that you understand the basics of a ColdFusion MX Component, select the text in the document and press the delete key.
Enter the following code into the document:
[View full width]<cfcomponent> <!--- Get a list of all our facilities ---> <cffunction name="listFacilities" access="remote" returntype= "query"> <cfquery name="listFacilities" datasource="oakbridge"> SELECT facility AS label, facilityID AS data FROM facilities </cfquery> <cfreturn listFacilities> </cffunction> <!--- Get a list of all our bookings for a particular facility on a particular date ---> <cffunction name="getBooking" access="remote" returntype="query"> <cfargument name="facilityID" type="numeric"> <cfargument name="theDate" type="numeric"> <cfquery name="getBooking" datasource="oakbridge"> SELECT *, UNIX_TIMESTAMP( startDate ) as bookingStart FROM booking WHERE facilityID='#facilityID#' AND startDate BETWEEN FROM_UNIXTIME( #theDate# ) AND FROM_UNIXTIME( #theDate# ) + INTERVAL 1 DAY </cfquery> <cfreturn getBooking> </cffunction> <!--- Add a booking ---> <cffunction name="addBooking" access="remote" returntype="string"> <cfargument name="facilityID" type="numeric"> <cfargument name="startDate" type="numeric"> <cfargument name="duration" type="numeric"> <cfquery name="addBooking" datasource="oakbridge"> INSERT INTO booking (facilityID, startDate, duration) VALUES ( #facilityID#, FROM_UNIXTIME( #startDate# ), #duration# ) </cfquery> <cfreturn "SUCCESS"> </cffunction> <!--- Remove Booking ---> <cffunction name="removeBooking" access="remote" returntype= "string"> <cfargument name="bookingID" type="numeric"> <cfquery name="removeBooking" datasource="oakbridge"> DELETE FROM booking WHERE bookingID=#bookingID# </cfquery> <cfreturn "SUCCESS"> </cffunction> </cfcomponent>
Is it any coincidence that the <cffunction> names have the same name as the method names called from our ActionScript (such as facilities.getBooking (facilitiesList.getSelectedItem().facility, _root.theDate.getTime ()/ 1000 );)? These functions are being called by ActionScript.
Save the document as facilities.cfc in the Oakbridge site's root.
If you preview the component in a browser (File, Preview in Browser), ColdFusion will show you all the information just entered, as shown in Figure 13.6, in a graphical manner.