Building Peachmail: Email Services
Joe has completed adding his friends to his Address Book and is now ready to use the email services of Peachmail! The email services include the capability to view, send, receive, and delete email from his account. We've already shown how sending an email works from the Address Book screen. You'll see that sending from the main email screen is not much different.
The Front-End: Flash MX
We covered the New User button and the Address Book button of the main email screen (Figure 7.15) in the previous sections. Now we'll look at the remainder buttons: Compose, Delete, Reply, and Forward.
Figure 7.15 The Peachmail main email screen.
Sending Email
The Compose, Reply, and Forward buttons involve the process of sending email, with the only difference being how the pop-up Compose screen is set up when the button is clicked. Take a look at the button handlers and notice how they all use the openCompose() method:
composeHandler = function () { // open the compose window openCompose(); } replyHandler = function () { // get the current email var email = emailMessages.getSelectedEmail(); // get the subject, and add "RE: " var subject = "RE: " + email.emailObject.subject; // get the from address var fromAddress = email.email; // make the message var message = "\n- Original Message -\n" + addTicks(emailText.text); // open the compose window, and set the variables openCompose(fromAddress, subject, message); } forwardHandler = function () { // get the current email var email = emailMessages.getSelectedEmail(); // get the subject, add "FW: " var subject = "FW: " + email.emailObject.subject; // get the message var message = "\n- Original Message -\n" + addTicks (emailText.text); // open the compose window with the variable openCompose("", subject, message); }
The openCompose() method on this screen is the same as the one in the Address Book screen. Generally, each openCompose() call fills in certain fields in the Compose screen depending on the handler. For instance, clicking Reply fills in the Compose screen's "To:" field, subject, and email body with the sender, subject, and email body of a selected email, respectively.
Clicking Forward fills in the Compose screen's subject and email body with the subject and email body of a selected email, and clicking Compose simply opens the Compose screen with nothing passed to it.
Notice how the Forward and Reply handlers require the selection of an email in order to fill in the Compose screen's fields with the proper content. This is achieved in the handlers by the line:
var email = emailMessages.getSelectedEmail();
"emailMessages" is an object of the custom emailList class used to manage the processing of the emails. We'll discuss the emailList class in more detail at the end of the chapter. For now, we can show that its getSelectedEmail() method calls the listbox component's getSelectedItem() method and its data variable to retrieve the selected email object:
emailList.prototype.getSelectedEmail = function () { // return the id return this.listBox.getSelectedItem().data; }
The data variable is an object called emailObject, which contains the contents of the email, that is, email.sender, email.email, email.subject, email.ReceiveDate, and so on.
One other thing to point out is the email body that is retrieved by the handlers in the line:
addTicks(emailText.text);
emailText is a textfield that displays the currently selected email message, and the function addTicks() simply prefixes markers to every line of the message body to indicate it's not part of the new email to be sent.
Once the Compose screen is open, it functions the same way as sending email that we discussed in the address book section.
Receiving Email
Peachmail is built to receive emails into a folder named Inbox. The Inbox folder is in a listbox called folders on the stage, and when it's clicked, Flash sends out a request to select the messages for that folder. To understand the detail of how the messages are retrieved and displayed to the screen, you need to understand how the messages are stored in Flash.
Peachmail contains a custom class called folderList():
global.folderList = function (listbox) { // store the listbox this.listbox = listbox; // set the change handler this.listbox.setChangeHandler("onChange", this); // store an array with the folders this.folders = []; }
folderList() is used to track all the folders for a specific user. Each folder is displayed on the screen by using Macromedia's listbox component. In addition, its details are stored in an array this.folders.
When the user logs into Peachmail, the application immediately executes getFolders() to retrieve the folders for the current user. As in the handlers discussed in the previous sections, this function performs a create-send-callback action to create the Get Folders XML request, send it to the server, and process the retrieved folder data.
Here is the ActionScript for getFolders and its callback:
getFolders = function () { // create the xml var getFoldersXml = buildGetFoldersRequest(); // create the call back getFoldersCallBack = function () { // create the folders list global.emailFolders = new folderList(folders); // if there are already messages, remove them folders.removeAll(); // create the onSelect callback emailFolders.onSelect = function (folder) { // get the id var id = folder.data; // get the emails getMessages(id); } // make the folders array foldersArray = []; // get the data node var dataNode = findDataNode(this.getDocIn()); // get the folders var folders = dataNode.firstChild.childNodes; // loop through the folders for (var i = 0; i < folders.length; i++) { // get the folder var folder = folders[i]; // get the id var id = folder.attributes.ID; // get the name var name = String(folder.firstChild.firstChild); // add the folder foldersArray.push({id: id, name: name, emails: []}); // add to the folders list emailFolders.addFolder(name, id); } } // send to server sendToServer(getFoldersXml, getFoldersCallback); }
You'll notice the getFoldersCallBack() callback function is quite heavy compared to the handler callbacks we've seen in the previous sections, so let's step through its process.
The first thing it does is to create a folderList object named emailFolders and to set the emailFolders.listbox variable to point to the folders listbox on the stage:
global.emailFolders = new folderList(folders);
Next, it clears the folders with the removeAll() listbox method, and then creates the onSelect() method for emailFolders. onSelect() is a key method for the retrieval of the email messages, as we'll discuss later. Then, it creates an array foldersArray. The callback then parses through the result data. For each folder it finds, it notes the folder name and folder ID, and proceeds to:
Store the name and ID into a folder record in foldersArray, along with an empty array named emails:
foldersArray.push({id: id, name: name, emails: []});
Add the name and id into the emailFolders object as a new folder:
emailFolders.addFolder(name, id);
The emailFolders.addFolder() method updates the folders listbox on the screen to display the added folder, in addition to updating the emailFolders' folders array with a folder record containing the folder name, folder ID, and the listbox index.
Here is the ActionScript for addFolder:
folderList.prototype.addFolder = function (name, id) { // add item to listbox this.listbox.addItem(name, id); // store in array this.folders.push({name: name, item: this.listbox. getLength(), id: id}); }
Now we can see how the handler updates the listbox on the screen. However, it's not so obvious how the code links a handler to the folders listbox for when a folder is clicked. Part of this involves understanding the Macromedia listbox component and how it handles changes.
When a user selects an item from the Macromedia listbox component, the onChange() handler of the listbox is automatically executed. The onChange() handler is not assigned by default, so it executes nothing in its default state. Recall the following line in the constructor for the folderList class:
this.listbox.setChangeHandler("onChange", this);
When the folderList object emailFolders was created in getFolders Callback(), it executed the preceding line thus assigning the folders listbox's onChange() to the folderList's onChange() method:
folderList.prototype.onChange = function () { // call callback function, with the selected folder this.onSelect(this.listbox.getSelectedItem()); }
Notice that the onChange() method executes the onSelect() method. onSelect() was defined in getFoldersCallback() and looks like this:
emailFolders.onSelect = function (folder) { // get the id var id = folder.data; // get the emails getMessages(id); }
onSelect() is passed the listbox's selected item, from emailFolders. onChange(). The folder ID is retrieved and then passed to getMessages(). The getMessages() function performs a create-send-callback action to get the messages for a specific folder. Of particular interest here is how much work the callback function getMessagesCallback() does in order to get the received data properly set up for viewing.
Here's the ActionScript for getMessages:
getMessages = function (folderId) { // create the xml var getMessagesXml = buildGetMessagesInFolderRequest (folderId); // create the callback getMessagesCallback = function () { // check for success if (wasSuccessful(this.getDocIn())) { // create the email list emailMessages = new emailList(messages); // remove all emails from the listbox messages.removeAll(); // create the handler emailMessages.onSelectEmail = function (emailData) { // get the details of the message getMessageDetails(emailData.id); // enable reply and forward buttons reply.setEnabled(true); forward.setEnabled(true); // check to see if there are attachments if (emailData.emailobject.hasAttachments == "True") { // enable the combo box attachmentCombo.setEnabled(true); } else { // disable the combo box attachmentCombo.setEnabled(false); } // keep track of last listbox selected global.selectedListBox = "emails"; } // create the delete handler emailMessages.onDeleteEmail = function (emailData) { // get the id var id = emailData.id; debug.print(":::" + id); // get the xml var deleteEmailXml = buildDeleteMessageRequest(id); // send to the server sendToServer(deleteEmailXml); } // get the data node var dataNode = findDataNode(this.getDocIn()); // get the messages var messages = dataNode.firstChild.childNodes; // loop through the messages for (var i = 0; i < messages.length; i++) { // get the message var message = messages[i]; // create a temp object var messageObject = {}; // get the id messageObject.ID = message.attributes.ID; // get the read status messageObject.New = message.attributes.New; // get the recieve date messageObject.ReceiveDate = message. attributes.ReceiveDate; // get whether it has attachments messageObject.HasAttachments = message. attributes.HasAttachments; // parse the sender to get the email and the name var sender = message.attributes.Sender.split ("("); // get the sender messageObject.Sender = sender[1].substring(0, sender[1].length - 1); // get the email messageObject.email = sender[0]; // get the subject messageObject.Subject = unescape(message. firstChild.firstChild.toString()); // create the attachments array messageObject.attachments = []; // add the message to the email list emailMessages.addEmail(messageObject); } } } // send the xml sendtoServer(getMessagesXml, getMessagesCallback); }
The callback function getMessagesCallback() sets up an emailList object named emailMessages using the messages listbox on the stage:
emailMessages = new emailList(messages);
emailMessages is created to facilitate all aspects of the messaging for a single folder. Next, the callback creates two handlers:
emailMessages.onSelectEmail() emailMessages.onDeleteEmail()
which will be called later, when a user selects and/or deletes email from the messages listbox. Finally, the getMessagesCallback() function loops through the received data and adds each message as an object into emailMessages with the following code:
emailMessages.addEmail(messageObject);
Taking a look at addMail() in detail shows how it updates the messages listbox on the screen using the listbox component's addItem() method:
emailList.prototype.addEmail = function (email) { // add the email to the email list this.listBox.addItem(email.sender, email.subject, email.ReceiveDate, {id: email.id, email: email.email, emailObject: email}); // get the length of the email list var length = this.listBox.getLength(); // reference the number with the ids object this.ids[email.id] = length - 1; }
When an item is selected from the messages listbox, its change handler is automatically invoked. Just like in the emailFolders Class constructor, the emailList Class constructor links the "messages" listbox's change handler to its onChange() method.
Here's the ActionScript for onChange:
emailList.prototype.onChange = function () { // call callback function, supply the id this.onSelectEmail(this.listBox.getSelectedItem().data); }
So when Joe clicks on a message from the messages listbox, emailList. onChange() calls the onSelectEmail() method, defined earlier in our call-back, triggering the viewing of the email. At this stage, however, all the emails for the inbox folder are loaded and ready to be viewed. Whew! Joe would be proud.
Viewing Email
We've shown that the user must initiate a request to update the messages listbox by clicking on a folder in the folders listbox, and you've seen that the getMessages() function, executed when the user clicks on a folder, populates the messages listbox with the subject headings of the messages from a given folder ID.
Without going into repetitive detail, getMessages() follows the same pattern as getFolders(): it performs a create-send-callback action to retrieve messages for the specified folder, and within the newly-defined callback, it generates the code necessary to view the message when a message is clicked. Recall that onSelectMail() is invoked when the user clicks on a message, and passes the messageID of the selected email to getMessageDetails():
getMessageDetails = function (messageID) { // create the xml var getMessageDetailsXml = buildGetMessageDetailsRequest (messageID); // create the callback getMessageDetailsCallback = function (messageID) { // check for success if (wasSuccessful(this.getDocIn())) { // get the data node var dataNode = findDataNode(this.getDocIn()); // get the message var message = dataNode.firstChild; // get the body var body = message.childNodes[2]; // get the id var emailId = message.attributes.ID; // get the email var email = emailMessages.getEmail(emailId). emailObject; // check to see if the email has attachments if (email.hasAttachments == "True") { // get the attachments var attachments = message.childNodes[3]. childNodes; // create an array to hold the attachments var attachmentsArray = []; // loop through the attachments for (var i = 0; i < attachments.length; i++) { // get the id var id = attachments[i].attributes.ID; // get the name var name = attachments[i].firstChild. firstChild.toString(); // add it to the array attachmentsArray.push({name: name, id: Õ id}); } // process the attachments processAttachments(attachmentsArray); } // set the text emailText.text = removeTags(body); } } // send the xml to the server sendToServer(getMessageDetailsXml, getMessageDetailsCallback, messageID); }
getMessagesDetails() uses the create-send-callback scheme and handles the retrieval of the message from the server, updating the stage's textfield with the text body of the email.
Deleting Email
Deleting email is a two-click process: the user first clicks on a message from the messages listbox, and then clicks the Delete button. Let's start off easy by looking at the Delete button's handler:
deleteHandler = function () { // delete selected emailMessages.deleteSelectedEmail(); }
Remembering that emailMessages is an object of the emailList() Class, we see that deleteSelectedEmail() looks like this:
emailList.prototype.deleteSelectedEmail = function () { // get the selected email var email = this.listBox.getSelectedItem().data; // get the email number var num = this.ids[email.id]; // remove from the listbox this.listBox.removeItemAt(num); // call the callback handler this.onDeleteEmail(email); }
deleteSelectedEmail() facilitates the deletion by removing the message from the listbox, and then calling onDeleteEmail(). For convenience, here is the onDeleteEmail() method again:
emailMessages.onDeleteEmail = function (emailData) { // get the id var id = emailData.id; // get the xml var deleteEmailXml = buildDeleteMessageRequest(id); // send to the server sendToServer(deleteEmailXml); }
Notice how onDeleteEmail() only passes one argument to sendToServer(), that being the XML request to delete the record from the database. No call-back is required because the server simply takes the request to delete and passes the change request to the database.
Let's move on to explore some of the transactional data between Flash and the server to carry out the actions for Email Services.