![]() Join Up! 96813 members and counting! |
|
|||
PHP and Adobe Air: Building a Time-tracking and Billing Application - Part II
Welcome back. In part 1 of this series, you created some
PHP remote services and the Clocked! widget application.
Part 2 covers PHP administration and completion of the timer
widget. Let's jump right into Adobe Flex Builder and add
more widget features.
Fill in the placeholder functions
You've already created placeholder functions in the
<mx:Script> block of the
clockedWidget.mxml file. Now it's time to fill them in,
beginning with the getClientsResult() function.
This function is called when the application gets the list
of clients from PHP. When that happens, the application
needs to respond by populating a drop-down list, or
combo box, with the names of the clients. The code
looks like this:
First, the function places the result of the remote object
call into the
client variable, an array. Next,
the dataProvider property of the
ComboBox with the ID of clientCB
is set to the clients array. The property that
contains each company name is company_name, so
the ComboBox's labelField is set
to that value. Finally, the application's state is changed
to loggedIn. But wait: Didn't you already do
that in the logInResult function? Yes, so you
need to delete or comment out the line that reads
currentState = 'loggedIn' inside the
logInResult function. Instead of changing the
state on a successful login, you instead get the list of
clients. Put this line in place of the now-removed state
change:
With this change in place, the
ComboBox will
already be populated when the user is presented with the
loggedIn view state. The next step is to get
the list of projects belonging to the client from which
users can select. First, add a change event
listener to the clientCB ComboBox. Doing so
allows you to call a function whenever a client is selected
from the list. Inside the clientCB's
<mx:ComboBox> tag, add this code:
Now, whenever a user chooses a client from the list, that
client's ID is sent to the remote PHP method
getProjects. And you've probably guessed that
you now need to complete the getProjectsResult
function to handle the response. The completed function
appears below:
The
getProjectsResult function is similar to
getClientsResult except that it populates the
projectCB combo box and specifies the
name property as the label field.
Now, when a user selects a client, that client's projects
will be retrieved and listed for the user.
Add support for the Ticket object
The next addition to the timer widget is support for the
custom
Ticket object. Recall that you already
created a Ticket class in PHP with Ticket.php.
For the class mapping in Zend_Amf to work, you need an Adobe
ActionScript version of Ticket.php. First, right-click the
src folder in the left pane of Flex Builder,
then click New > Folder. When you create
custom classes in ActionScript (as in many other languages),
the protocol is to use dot-syntax to avoid confusion. It is
also accepted practice to use domain names, as they are a
handy way of ensuring uniqueness. Because I own the domain
name flexandair.com, I create the folder structure
com/flexandair/ and place my custom classes in
the flexandair folder. For convenience, you'll
want to do the same for this exercise. You can then access
the classes in ActionScript by importing them from
com.flexandair.MyClass. To set this up in your
project, you can enter the full path in the New
Folder dialog box: /com/flexandair.
Click Finish, and both directories will be
created.
Next, right-click the
flexandair folder,
then click New > ActionScript Class. For
the class name, type TicketVO. Click
Finish, and the basic class file is
generated. The first things you need to add to this file are
the [Bindable] tag and an alias definition for
class mapping. Add these two lines right below the package
declaration and opening bracket, on lines 3 and 4:
The
[Bindable] tag simply allows data binding
to this class's properties. The next line is to indicate
that this class is mapped remotely as TicketVO.
Next, add the same properties here in ActionScript that you
added before to the Ticket.php file. Add these lines right
below the class declaration and opening bracket, before the
TicketVO constructor function:
As you can see, this file is similar to its PHP counterpart,
and that's all there is to it.
Develop the timer
Before you can send tickets to PHP, you need to add the
inner-workings of the timer. ActionScript contains a built-
in
Timer class, but despite its name, it isn't
well suited for use as a timer in the literal sense. Because
it relies on the application's frame rate, time calculations
can vary significantly between computers. For this
application, you use a custom StopWatch class.
You can download the class file, StopWatch.as. I don't cover this
class in line-by-line detail, but when you open it, you'll
see that it is copiously commented and pretty
straightforward. After you've downloaded the file, place it
in the src/com/flexandair folder of your
project alongside Ticket.as. Back inside the script block of
clockedWidget.mxml, add another import
statement below the Alert import to bring in
these new classes:
You also need to keep track of whether the clock is running.
To do that, add a Boolean variable called
isRunning. You also need variables for the
active ticket, the user's hourly rate, the time increment
used for billing purposes, and the billable amount accrued.
Add these lines below the import statements:
To actually start the timer, use a function called
startTimer():
private function startTimer():void {
swLabel.addEventListener(Event.ENTER_FRAME, updateTime);
stopWatch.start();
isRunning = true;
}
The first line tells the clock-like label to run the
updateTime function you'll create in a moment
on each new frame. Then, the stopWatch's
start method is called. Finally, the
isRunning variable is set to True. Next, add
the updateTime function referenced above:
The
updateTime function sets the clock
label to the string that the custom
StopWatch class returns. Then, the accrued
value of the time elapsed is calculated, and that value is
displayed in a label control. You'll see an ID you haven't
created yet: myCF. Use an MXML component called
a CurrencyFormatter to create a string
from the raw number. To create the myCF
CurrencyFormatter, place this MXML code above your
script block:
The above
CurrencyFormatter's
format() method produces a string with a dollar
sign ($), a decimal, and two decimal places.
Allowing users to control the timer
With functions to start the timer and display pertinent
information to users, you next need a function to start and
stop everything at a user's instruction. It makes more sense
to have just one button that both starts and stops the
clock. The clock should operate like a switch, with the vast
majority of user actions being either
stop or
start requests. To make the single button work
this way, simply check the isRunning Boolean
value whenever the button is clicked:
As you can see, whenever the button is clicked, the state of
the stopwatch is reversed and so is the label on the button.
Note that
Stop only means pause; in
other words, the counter is not reset, and no values are
cleared. To actually reset the timer, you need to use
another function:
Like the
pause code in the
buttonClick() function, the reset
function stops the clock and sets the button's label to
Start. It also sets the
stopWatch variable to a completely new instance
of the StopWatch class, destroying any saved
values from the previous instance. Finally, the clock label
is updated to reflect the reset. Before doing anything else,
add the click event listener to the clockBtn
button control by adding click="buttonClick()"
to its MXML tag. You should now be able to run the
application, log in, then choose a client and project from
the combo boxes. Figure 1 shows the application in its
loggedIn state.
Figure 1. The running application
Submitting tickets
To begin the process of submitting a new ticket, you need to
add a Save button. In the Design view,
select the
loggedIn view state, then drag a
button onto the application. Type Save for the
label, and assign beginSubmit() as its on-click
function.
One more piece of information you
need from the user is the note for the active ticket. The
widget application is meant to take up as little desktop
real estate as possible. To that end, you won't insert a big
text area control right on the widget for note entry.
Instead, you'll use ActionScript to create a new native
window containing a text area and a prompt for the user to
add a note. Inside the new window, place a simple custom
MXML component to accept user input and submit the ticket.
To start, right-click the
src/com/flexandair
folder, then click New > MXML Component.
Name the component SaveForm, and set its width
to 240 pixels and its height to 130 pixels. Click
Finish, and the new component will be
created and open in the main pane. Add the following lines
of MXML to create a simple form:
Then, create a new
<mx:Script> block.
At the top of the script block, import your custom
TicketVO class along with the
mx.core.Window and
mx.controls.Alert classes, then declare an
activeTicket variable:
You probably already know that you need a way to close
the new window, so add the
closeWindow
function:
Because
this is the SaveForm
component and its direct parent is the pop-up window, you
can access the parent using this.parent. Then,
the window's built-in close() method is called.
Back in the main MXML file's script block, add the code to
open and display the window. First, you need to add an
import statement here for the
mx.core.Window class like you did in the
SaveForm. Then, add this function:
Basically, this function creates a new window, sets some
of its properties, and adds the custom
SaveForm. Then, another
functionassembleTicket()is called
to bring together your TicketVO object. Here's
that function:
This function first stops the clock. Then, several of the
ticket's key properties are setin fact, all the
properties you know so far. The user adds the category and
notes/description in the new window. The time stamps and
duration are divided by 1000 so that you're working with
seconds rather than milliseconds.
Back in the SaveForm.mxml component, add a new
<mx:RemoteObject> like the one in
clockedWidget.mxml. Add this outside the script block:
Now, go back to the script block, and add the
saveResult function mentioned above. It looks
like this:
To actually send the completed ticket to PHP, construct a
new function called
sendTicket():
This function adds the category and description to the
ticket, then sends it using the
ticketService
remote object. Then, the saveResult function is
called, the pop-up window is closed, and a success message
is displayed.
With the widget's basic features complete, you're ready to
move on to the PHP management section.
The PHP Interface
Because this article assumes that you're familiar with PHP
website development, I won't cover the details of the PHP
interface. You can find all the code you need to make it
work in this article's source archive. Instead, I walk you
through a sample use case for the clocked! PHP application.
Please keep in mind that code distributed with these
articles should not be deployed in a production environment;
use them only on your local machine for testing.
Basic workflow
To begin testing and using the Clocked! application, you
first need to place the PHP files on your web server.
Download the website.zip file included with this article,
and extract its contents to a folder beneath your web root.
Be sure to not overwrite any of your existing files. Then,
locate the DBprops.php file. Change the variable values in
this file to reflect your MySQL host, user, and password.
You also need to set this information in the
DBConnection.php file. The example website uses Zend_Db for
database access, so be sure you give the DBConnection.php
file the same access to the Zend Framework as you did in
part 1 for the Zend_Amf files, like gateway.php.
First, create a new client. To do this, navigate to the
folder in which you placed the example site in your web
browser. This article assumes that you're using the web root
folder (http://localhost/). You'll see some choices along
the top, as shown in Figure 2.
Click Clients to see a table listing any
existing or sample clients. Click New
Client to create a new one. When that's complete,
click Add Project in the right-most column
of the client list to add a new project for this client.
When you have completed that, you're ready to run the
Clocked! widget and add a timed ticket to your new client
and project.
Run the widget for as long as you like. As is customary in
timed billing, after the first second elapses, an increment
is added to the billable total. The default increment is 15
minutes, or 0.25 hours. So, a billable time of 1 second will
be invoiced as 0.25 hours.
After you stop the widget and save the new ticket, you can
return to the PHP web interface and view it in the Tickets
section. To test generating an invoice, go back to the
Clients page. Next to your test client, click Add
Invoice in the left-most column. All the available
tickets for the selected client are displayed, and you can
choose which to include in the invoice. Then, enter an
invoice date and a due date, and click
Submit. Your invoice will be generated and
presented to you in Hypertext Markup Language (HTML) form in
the browser window. For your convenience, a "Generate PDF"
function is also included, which generates a Portable
Document Format (PDF) file using the open source, stand-
alone
dompdf library and prompts you for a
download.
What's next?
Now that you have a basic version of the Clocked!
application, feel free to extend it further. You could add
PHP authentication,
sendmail integration, and
further database features. You can also generate some really
amazing PDF invoices with dompdf. See the official
website for complete information and examples.
The widget Adobe AIR application has virtually limitless
options for extending and customizing your users'
experience. You might want to use a custom window
chrome, or graphical skin, or add "minimize to
tray" functionality. You can even add drag-and-drop file
uploads to associate documents or other files with a
particular client or project. To begin exploring all the
exciting possibilities, visit the Adobe AIR Developer
Center and the AIR
for Flex Developers website.
|