User Interactions


Now that the user is authenticated in the application, the bulk of the application processing can take place. Standard users go through the following application logic.

If the user is connected to the Internet:

  • Reports stored locally (while offline) are automatically stored in the database.

  • Any returned reports (admin denied the expense report) are displayed for editing and resubmittal.

  • New reports can be started by entering a report name and choosing a date range.

  • New reports and returned reports are both edited by utilizing an expense wizard that corresponds to the type of the expense item.

  • Reports are submitted back to the database for retrieval by an admin.

If the user is not connected to the Internet:

  • Any pending locally stored reports are displayed for editing.

  • New reports can be started by entering a report name and choosing a date range.

  • Reports are stored locally for automatically retrieval when the user is connected to the Internet.

The following describes what the teams had to do to get these interactions to work correctly in both Flash MX and ColdFusion MX.

New Expense Report

The New Expense Report movie clip form (Figure II-3.8) is designed to have an input field for the expense report title, a button to display the date range of the report, a button for completing the setup, and some text fields for displaying the selected date range. The form will default to only allowing input of the title at first; this is accomplished through a combination of setEnabled(false) for buttons and _visible=0 for movie clips and text fields.

Figure II-3.8. The movie clip form for new expense reports has all of the objects necessary start an expense report.

graphics/02fig14.gif

 myDateRange.slash._visible=0; myDateRange.theCal._visible=0; myDateRange.showcal_pb.setEnabled(false); myDateRange.dateDone_pb.setEnabled(false); myDateRange.expenseNameField.onChanged=function(){ if(myDateRange.expenseNameField.text!=""){ myDateRange.showcal_pb.setEnabled(true); }else{ myDateRange.showcal_pb.setEnabled(false); } } 

All of these lines refer to objects that reside on a movie clip instance named myDateRange, thus the myDateRange in front of the objects. First the slash between the dates and the calendar component are both hidden using the _visible=0 property. All this does is make the objects invisible and inaccessible to the end user. Then the showcal_pb and dateDone_pb push buttons have their setEnabled properties set to false. This keeps the buttons viewable but grays them out so that the user cannot click them. Finally, the expenseNameField has a method added to it called onChanged. This is one of the new TextField methods opened up in Flash MX. Now Flash will perform the function assigned here whenever the text field changes. In this function, showcal_pb is enabled whenever the expenseNameField has text in it, and is disabled whenever it is empty.

DateRange

To utilize the calendar component, the team will place an instance of the component named thecal on the myDateRange movie clip (Figure II-3.9). The team will also add a push button called calDone_pb to allow the user to indicate when he or she has selected a date range. Both of these objects will be hidden and unhidden by the onShowCal button. To make the button control the display, the team will add a setClickHandler method to it. This method has two properties: the name of the function that will be called when the button is clicked, and the movie clip where the function is located.

Figure II-3.9. The movie clip form for the date selection allows selection of a date range for the expense report.

graphics/02fig15.gif

 myDateRange.showcal_pb.setClickHandler("onShowCal",this);  functiononShowCal(){ myDateRange.calDone_pb._visible=1; myDateRange.theCal._visible=1; } 

The team can also add the following code to force the user to select a date range:

 myDateRange.theCal.setSelectionRequired(true);  

The setSelectionRequired method of the calendar object controls whether the calendar object requires at least one date to be selected at all times.

When the calDone_pb is clicked, the application should take the selected date range and display the start and end values in the dateStart and dateEndtextfields.The team has added code to the onCalDonefunction to perform this action.

 //setmonthnamesfordisplaypurposes  monthNames=newArray("Jan","Feb","Mar","Apr","May", "Jun","Jul", "Aug","Sep","Oct","Nov","Dec"); //setdatelisttotheselecteddates datelist=myDateRange.thecal.getSelectedItems(); //reversetheselectionifitisbackwards if(datelist[0] >datelist[datelist.length-1]){ datelist.reverse(); } //setthetextfieldsontheformtothestartandenddates myDateRange.dateStart.text= monthNames[datelist[0].getMonth()]+''+ string(datelist[0].getDate())+','+ string(datelist[0].getFullYear()); myDateRange.dateEnd.text= monthNames[datelist[datelist.length-1].getMonth()]+''+ string(datelist[datelist.length-1].getDate())+', '+string(datelist[datelist.length-1].getFullYear()); 

This code starts out by creating an array of month values that will be used to convert number values for months to their string equivalents. Next the variable datelist is set to equal the array of dates returned from the calendar object. This is accomplished through the getSelectedItems method, which simply returns an array of the selected values.

Because the date object returns the dates in the order in which the user selects them, the team needed a way to reverse the list if the user selected the values from right to left (in reverse order). The if statement checks the first value in the array (datelist[0]) against the last value in the array, (datelist[datelist.length-1]). If the first value is greater than the last value, then the dates are in the wrong order and need to be reversed via the array.reverse function.

instant message

Flash Arrays start at position zero and end at the array length-1. Also, array values can be accessed simply by using the array name and an array value surrounded by brackets.


Finally, the datestart and dateend text fields are set to equal a string equivalent of the first and last values in the date array.

Report-List Pages

For each day in the selected date range, the application needs to duplicate the report-list movie clip (Figure II-3.10). This form will show a listbox component, back and forward buttons (to move from one page to another), and a page expense total.

Figure II-3.10. The movie clip form for the expense items list allows the user to view expense items for each day in the date range.

graphics/02fig16.gif

Once the initial form is created, the team needs to add a new instance of it to the stage for each date in the selected date range.

 pageArray=newArray(); for(i=1;i<=datelist.length;i++){  duplicateMovieClip("page1","page"+i,i);//makeanewdate page  pageArray[i]=eval("page"+i);  pageArray[i]._x=222;  pageArray[i]._y=70;  pageArray[i].pageNum=i;  pageArray[i].pageDate.text=MonthNames[datelist[i- 1].getMonth()]+''+string(datelist[i-1].getDate())+','+ string(datelist[i-1].getFullYear());//setpagedate } 

The team has also set an array called pageArray to contain references to the new pages as they are created. This will allow easier access to the pages and allow them all to be contained in one object. Now the team can reference a page by pageArray[pagenumber] to access objects of the specific page.

Finally, the team members need to begin the structure that will contain all of the expense report data. First they will add the following lines before the for loop to create the initial structure:

 pendingObject=newObject();  pendingObject.reportTitle=myDateRange.expenseNameField.text; pendingObject.reportTotal=0; pendingObject.expenseDataArray=newArray(); 

Then for each iteration through the loop, the pending object will be updated to include the expense report information:

 pageObject.pageNum=i; pageObject.pageDate=MonthNames[datelist[i-1].getMonth()]+ ''+string(datelist[i-1].getDate())+','+string(datelist [i-1].getFullYear()); pageObject.pageExpensesArray=newArray(); pendingObject.expenseDataArray[i]=pageObject; 

The overall expense object will be somewhat complicated. See Figure II-3.11 for the completed object structure.

Figure II-3.11. The structure for the expense object is complicated, but is needed to store all of the expense report details.

graphics/02fig17.gif

While this structure may seem overly complex, it will adequately hold all of the information for the expense report. Also, since the team will be taking advantage of Flash Remoting and ColdFusion MX's native handling of Flash objects, the team will only need to construct it once and then store it in the database.

Expense Wizard

After the pages are created, the expense wizard needs to be shown to the user. This movie clip will have more than one frame, and will contain each of the expense wizards and an initial screen that has a combo box where the user can select an expense report (Figure II-3.12).

Figure II-3.12. The stage for the expense wizard movie clip shows the objects and timeline for the various expense wizards.

graphics/02fig18.gif

To utilize the combobox component, the team will drop an instance of the combobox named wizardType_cb on the first frame of the expenseWizard movie clip. The team can enter the selectable wizards into the combobox by double-clicking the Labels section of the component Properties panel. This shows a list that can be added to, deleted from, and rearranged at will.

instant message

Some component properties can be set directly in the Properties panel of Flash MX. Others need to be set with ActionScript; see the ActionScript Dictionary for more information.


To control how the movie clip behaves when the user selects from the combobox, the team added the following code to the main movie clip:

 if(MyWizard.WizardType_cb.getSelectedIndex()==1){  MyWizard.gotoAndPlay("Air"); }elseif(MyWizard.WizardType_cb.getSelectedIndex()==2){  MyWizard.gotoAndPlay("Ent"); }elseif(MyWizard.WizardType_cb.getSelectedIndex()==3){  MyWizard.gotoAndPlay("Meal"); } 

This will check the wizardType_cb on the MyWizard movie clip, and getSelectedIndex will get the value of the item the user selects from the combobox. The team created an if/else case statement that will play specific frames based on which selection is chosen. The team also set up each wizard to have a named frame label (Air, Ent, Meal, etc.).

The team then created the specific wizard forms for the various types of expense reports. Figure II-3.13 shows the appearance of the airfare expense wizard for the application.

Figure II-3.13. The movie clip for the airfare expense wizard allows the user to enter all of the details necessary for an airfare expense item.

graphics/02fig19.gif

The code for each of the wizards will look relatively similar; they will handle adding new expense items to the listbox component on the expense pages, as well as updates and deletes to existing items in the listbox. For updates and deletes, the listbox will send an updateflagvariable to tell the function to handle it differently.

When the updateflag variable is set to true, the addItem_pb pushbutton component will have its label changed from "Add Item" to "Update Item" via the setLabel method of the pushbutton component.

 //iftheuserisupdating  if(updateflag){  addItem_pb.setLabel("UpdateItem");  additem_pb.setEnabled(true);  removeitem_pb._visible=1; } 

When the Add button is clicked, the fields of the wizard are added to the expenseDetails array. To make sure that all of the expense wizards work the same, the team always sets the first item of the array to the price of the expense item and the last item to the expenseLabel variable that will be displayed in the expense listbox. The expenseLabel is simply the description of the expense item plus the price of the expense item:

 //addorupdatereportlistifclicked  //createarrayforexpensedetails expenseDetail=newArray(); expenseDetail[0]='1'; expenseDetail[1]=priceField.text; expenseDetail[2]=ticketNumField.text; expenseDetail[3]=startCityField.text; expenseDetail[4]=endCityField.text; expenseDetail[5]=commentsField.text; //setexpenselabel expenseLabel="Airfare"+priceField.text.moneyformat(); expenseDetail[6]=expenseLabel; 

Next, the expense reportList_lb listbox and the pendingobject. expenseDataArray object will need to be either updated or added to.

Since the expense wizards sit on their own movie clip, the team will need to use the _parent object to reference the expense listbox and the data object on the main timeline.

 //addorupdatethelabelandarrayinfo if(updateflag){ _parent.pageArray[_global.currentpage].reportList_lb.replace ItemAt(_parent.pageArray[_global.currentpage].reportList_ lb.getSelectedIndex(),  expenseLabel,expenseDetail); _parent.pendingobject.expenseDataArray[_global.currentpage] .pageExpensesArray[_parent.pageArray[_global.currentpage] .reportList_lb.getSelectedIndex ()]=expenseDetail; 

In this code, if the updateflag variable is set to true, the reportList_lb listbox on the current page of the pageArray array is updated using the replaceItemAt method of the listbox component. The replaceItemAt method takes three parameters: the item index to replace, the label to replace the current listbox label, and the data to replace the current listbox data. In this case, the item index to replace is the current selected item returned from the getSelectedIndex method.

The pendingobject object also needs to be updated, as this will be where the data will ultimately be stored. For this, the team simply replaced the current expenseDataArray element with the expenseDetail array.

If the updateflag variable is false, then the item will need to be added to the pageArray.reportList_lb listbox and the pendingobject. expenseDataArray object rather than updating them.

 }else{ _parent.pageArray[_global.currentpage].reportList_lb. addItem(expenseLabel, expenseDetail) _parent.pendingobject.expenseDataArray[_global.currentpage]. pageExpensesArray.push(expenseDetail); } 

In this code, the pageArray.reportList_lb listbox component is updated via its addItem method. This component takes two parameters, one for the listbox item label, and one for the listbox item data. Also, the array.push method will add items to the pageExpensesArray array. The push method adds an item to the end of the pageExpensesArray that contains the expenseDetail array.

The last major thing a user can do is remove an expense item. The code for removing the item from the listbox will use the removeItemAt method of the listbox component. This method only requires the index of the item to be removed; in this example the team removed the item that was selected. To remove the item from the pageExpensesArray array, the team will use the array.splice command. takes two properties: the position to start removing from, and the number of items to remove. In this case, the position will be the selected index of the listbox, and the number of items to remove will be one.

 //removeitemfromreportlistifclicked _parent.pageArray[_global.currentpage].reportList_lb. removeItemAt(_parent .pageArray[_global.currentpage].reportList_lb.getSelected Index()); _parent.expenseDataArray[_global.currentpage].pageExpenses Array.splice (reportList_lb.getSelectedIndex(),1); 

Store Reports

The last step for the user is to complete the expense report by clicking the Submit or Cancel button on the myComplete movie clip. This movie clip will show the buttons and a total for the entire report (Figure II-3.14).

Figure II-3.14. The movie clip form for completing the expense report shows the expense report total, and allows the user to Submit or Cancel the report.

graphics/02fig20.gif

At this point, the team needs to prepare to store the object locally on the user's machine (in case there is no Internet connection). This is accomplished through the use of the local shared object.

Local shared objects, a new concept for Flash MX, were created to mimic the effect of browser cookies in traditional Web applications. The team can use them to store information on the user's local machine that will persist until the user connects to the Internet and the information is added to the database. Local shared objects are stored in a Macromedia proprietary format.

The user ultimately has control over how local shared objects are stored on his or her own computer. By right-clicking on the Flash movie, a user can alter the Flash Player settings (Figure II-3.15).

Figure II-3.15. Right-clicking on a Flash MX movie allows access to the Flash Player storage-settings display.

graphics/02fig21.gif

From here, the user can select the amount of local storage he or she wants to allow for a specific domain. If the user selects an amount of storage that is too small, he or she will be warned that local information will be deleted (Figure II-3.16).

Figure II-3.16. The Flash Player storage-settings warning will inform users that they have not allowed enough storage for the application.

graphics/02fig22.gif

If this occurs, and the expense application tries to store local data, the user will be prompted to allow the extra storage (Figure II-3.17).

Figure II-3.17. The Flash Player storage request asks the user for permission to store information locally on their machine.

graphics/02fig23.gif

Obviously, if the user still declines the extra storage, the expense application will not be able to store any local data. Because of this, the Vshift team will need to educate the MonkeyTongue sales team before delivering the application.

To work with the local shared objects in Flash, the team members will use the getLocal method of the SharedObject object. They will initialize a local shared object by setting a variable called sobj equal to the local object named pendingReports in the initialization function of the main timeline:

 //initializesand/orgetsthelocalsharedObject sobj=SharedObject.getLocal("pendingReports"); 

To write to a local shared object, the team members simply need to set sobj.data.variablename equal to the value they want to write. The first thing they need to do with the local shared object is to check for an array object stored in the sobj, and if it is not there, they will create one.

 if(sobj.data.pendingReportsArray==undefined){  sobj.data.pendingReportsArray=newArray(); } 

Now that the local shared object and the pendingReportsArray array is set up, the team can store the expense report object locally if the user is offline.

 sobj.data.pendingReportsArray[sobj.data.pendingReportsArray. length] =pendingObject; sobj.flush(); 

The team simply sets a new element on thesobj.data.pending ReportsArray array to the pendingObject object. After the values are set, the team needs to call the flush method of the local shared object. Typically, data is written to local shared objects when the Flash Player is shut down; however, the flush method can be used to write the local data immediately. This is all the team has to do to add a new expense report to the user's local computer.

Next, if the user is online, the ActionScript needs to store the expense report in the database rather than in a local shared object:

 arrayContainer=newArray();  arrayContainer[0]=pendingObject; CFCService.storeExpenseReport(_global.userid, arrayContainer[0].reportTitle, arrayContainer[0].ReportTotal, arrayContainer); 

In this code, if the user is online, the application creates the arrayContainer array to store the pendingObject object. Next, the CFC function storeExpenseReport (see the code below) is called with the arguments userid, reporttitle, and reporttotal, and the arrayContainer array.

The storeExpenseReport CFC function receives the Flash arguments with the <CFARGUMENT> tag. Before the Flash arrayContainer array can be stored in the database, it must be converted to WDDX via the <CFWDDX> tag. Finally, the team added the SQL insertdata query to insert the values into the database.

instant message

ColdFusion MX treats Flash arrays differently than Flash objects. Flash objects are sent as a set of named arguments, whereas Flash arrays are sent as one structure. We found it easier to send a one-element array to ColdFusion MX and always reference the first parameter.


 <cffunctionname="storeExpenseReport"access="remote" roles="user"hint="Storesthelocalexpensereportinadb">  <cfargumentname="userid"type="numeric"required="yes" hint="Theusersid">  <cfargumentname="expensetitle"type="string" required="yes"hint="Thetitleoftheexpensereport">  <cfargumentname="expensetotal"type="numeric" required="yes"hint="Thetotaloftheexpensereport">  <cfargumentname="theobject"required="yes"hint="Theflash objectthatstoresthereports">  <cfwddxaction="CFML2WDDX"input="#arguments.theobject#" output="wddxText">  <cfqueryname="insertdata"datasource="expensedb">   INSERTINTOreports(userid,total,submitdate,title, wddxdata,status)   VALUES(#arguments.userid#,#arguments.expensetotal#, #createODBCDate(now())#,'#arguments.expensetitle#', '#wddxText#','pending')  </cfquery> </cffunction> 

Retrieve Old Reports

With the application storing expense reports in the database and in local shared objects, the team can now program the application to retrieve returned reports from the database and to retrieve pending locally stored reports. For this, the team must add some ActionScript to the initialization function of the main timeline, in the application's user state:

 if(sobj.data.pendingReportsArray==undefined){  sobj.data.pendingReportsArray=newArray(); }elseif((_global.online)and (sobj.data.pendingReportsArray.length >0)){ for(i=0;i<=sobj.data.pendingReportsArray.length-1;i++){ arrayContainer=newArray(); arrayContainer[0}]=sobj.data.pendingReportsArray[i]; CFCService.storeExpenseReport(_global.userid, sobj.data.pendingReportsArray[i].reporttitle, sobj.data.pendingReportsArray[i].reporttotal, arrayContainer); } sobj.data.pendingReportsArray=newArray(); //storedlocalreportsindb } CFCService.getReturnedReports(_global.userid); 

The first part of this code, which has already been added to the application, checks to see if the local shared object has a pendingReportsArray array already in it. The second part of the code happens when there is already a pendingReportsArray array and the user is online. This tells the application to loop through the local array and add the contents of each element to a one-element array for storage in the database (see the Post-It earlier in the chapter that explains the use of a one-element array). To store the pending expense report data, the team uses the existing storeExpenseReport CFC function.

Next, if the user is online, the CFC function getReturnedReports is passed the user's user ID as an argument via a function call.

 <cffunctionname="getReturnedReports"access="remote" roles="user"hint="Queriesthedbforreturnedreports">  <cfargumentname="userid"required="yes"hint="Theuseridto getreportsfor">  <cfsettemparray=arraynew(1)>  <cfqueryname="getdata"datasource="expensedb">   SELECTid,wddxdata   FROMreports   WHERE(userid=#arguments.userid#)AND(status='returned')  </cfquery>  <cfsetcount=1>  <cfloopquery="getdata">   <cfwddxaction="WDDX2CFML"input="#getdata.wddxdata#" output="convertedtext">   <cfsetconvertedtext[1].dbid=getdata.id>   <cfsettemparray[count]=convertedtext[1]>   <cfsetcount=count+1>  </cfloop>  <cfreturntemparray> </cffunction> 

This CFC function checks the database for reports submitted from the user ID where the status equals 'returned.' This will hand back all of the reports that were denied and returned to this user. These results are then looped through and converted from WDDX back into a ColdFusion MX structure (which Flash MX can receive as a native Flash object). A dbid is added to the object for each item in the array; this will point to the database row that needs to be updated when the user resubmits his or her report.

Flash MX will automatically call the function getReturnedReports_ Result if the CFC function returns a result:

 functiongetReturnedReports_Result(result){  if(result.length >0){ _root.returnedReports=result; trace('addeddbreportstolist'); addToList(result); }else{ trace('noresults'); } } 

This function will check to see if there are any returned expense reports and will call the function addToList, passing the results to it. The addToList function will loop through the resulting returned reports and add the items to the oldReports_lb listbox on the old-reports movie clip (Figure II-3.18).

Figure II-3.18. The movie clip form for the old-reports list contains the objects necessary to show returned reports.

graphics/02fig24.gif

The old-reports movie clip will have the oldReport_lb listbox, as well as buttons to view the old reports and to delete the old report. When the View button is clicked, the expense report will be loaded from the expense data. The code for this will set the pendingObject object equal to the selected array element from the returnedReports array. Since old reports will be updated instead of added, the team will use the returned ReportID variable to track either the database ID or the array element of the chosen expense report. If the user is online, the returnedReportID will be the dbid stored in the returnedReport information. If the user is offline, the returnedReportID variable will be set equal to the selectedIndex method of the oldReport_lb listbox component. Finally, the datelist will be created to hold the date range that spans the old expense report. This datelist will be an array that stores each pageDate element of the pendingObject.expensedataarray array Once the datelist array is created, the application passes it to the createPages function:

 pendingObject=returnedReports[myOldReports.oldReports_lb.  getSelectedIndex()] if(_global.online){ returnedReportID= returnedReports[myOldReports.oldReports_lb.getSelectedInd ex()].dbid; }else{ returnedReportID=myOldReports.oldReports_lb.getSelected  Index(); } datelist=newArray(); for(i=1;i<=pendingObject.expensedataarray.length-1;i++){ datelist[i-1]=pendingObject.expensedataarray[i].pageDate } createPages(datelist); 

From here, the createPages function will re-create each of the expense pages and add the expense items to each page's listbox.

If the Delete button is clicked, the ActionScript will remove the item from the listbox and either splice the item out of the array (if the user is offline) or call the CFC function deleteReport).

 myOldReports.oldReports_lb.removeItemAt(myOldReports.oldR eports_lb.getSelectedIndex()); if(_global.online){ CFCService.deleteReport(returnedReports[myOldReports.oldR eports_lb. getSelectedIndex()].dbid); }else{ sobj.data.pendingReportsArray.splice(myOldReports.oldRepo rts_lb. getSelectedIndex(),1); } 

The deleteReport CFC function will simply update the status of the database ID that is sent to it, to "deleted." This will remove the expense report from being seen anywhere in the application but will leave it in the database for archives or reporting.

 <cffunctionname="deleteReport"access="remote"roles="user" hint="Deletesagivenreport">  <cfargumentname="dbid"required="yes"hint="Theidofthe recordtodelete"> <cfqueryname="deleteReport"datasource="expensedb"> UPDATEreports SETstatus='deleted' WHERE(id=#arguments.dbid#) </cfquery> </cffunction> 


Reality Macromedia ColdFusion MX. Macromedia Flash MX Integration
Reality Macromedia ColdFusion MX: Macromedia Flash MX Integration
ISBN: 0321125150
EAN: 2147483647
Year: 2002
Pages: 114

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net