This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
The information in this book is distributed on an "as is" basis, without warranty. Although every precaution has been taken in the preparation of this work, neither the author(s) nor Apress shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the information contained in this work. The source code for this book is freely available to readers at www.friendsofed.com in the Downloads section.
Credits Lead Editor Ben Renow-Clarke
Technical Reviewer Kevin Ruse
Production Editor Ellie Fountain
Compositor Patrick Cunningham
Editorial Board
Proofreader
Clay Andres, Steve Anglin, Mark Beckner, Ewan Buckingham, Tony Campbell, Gary Cornell, Jonathan Gennick, Michelle Lowman, Matthew Moodie, Jeffrey Pepper, Frank Pohlmann, Ben Renow-Clarke, Dominic Shakeshaft, Matt Wade, Tom Welsh
Lisa Hamilton Indexer Broccoli Information Management
Artist Project Manager Beth Christmas
April Milne
Cover Image Designer Copy Editor Marilyn Smith A s s o c i a t e P r o d u cction t i o n DDirector irector Kari Brooks-Copony
Corné van Dooren
Interior and Cover Designer Kurt Krames
Manufacturing Director Tom Debolski
CONTENTS AT A GLANCE About the Author
xvii
About the Technical Reviewer
xix
About the Cover Image Designer
xxi
Acknowledgments Introduction Chapter 1 INTRODUCTION TO XML
xxiii xxv 1
Chapter 2 GENERATING XML CONTENT
33
Chapter 3 ACTIONSCRIPT 3.0 AND XML
65
Chapter 4 USING E4X EXPRESSIONS
97
Chapter 5 USING THE URLLOADER CLASS WITH XML DOCUMENTS
135
Chapter 6 LOADING METHODS SPECIFIC TO FLEX
169
Chapter 7 LOADING METHODS SPECIFIC TO FLASH
203
Chapter 8 MODIFYING XML CONTENT WITH ACTIONSCRIPT 3.0
233
Chapter 9 COMMUNICATING WITH THE SERVER
279
Chapter 10 CONSUMING WEB SERVICES WITH FLEX
329
Chapter 11 CONSUMING WEB SERVICES WITH FLASH
373
Chapter 12 FLASH CASE STUDY
407
Chapter 13 FLEX CASE STUDY
439
Index
480
v
CONTENTS About the Author
xvii
About the Technical Reviewer
xix
About the Cover Image Designer
xxi
Acknowledgments Introduction
xxiii xxv
Chapter 1 INTRODUCTION TO XML
1
What is XML? Understanding XML Storing information in XML documents XML, in the beginning An XML example Why XML? Simple Flexible Descriptive Accessible Independent Precise Free Why is XML important in Flash and Flex? XML as a SWF data source MXML in Flex ActionScript 3.0 and XML XML document sections Document prolog XML declaration Processing instructions Document Type Definitions Document tree Whitespace Namespaces
CONTENTS Structuring XML documents Elements Writing elements Naming elements Populating elements The first element Attributes Writing attributes Naming attributes Structuring attributes as elements Text Entities Comments CDATA A simple XML document Understanding well-formed documents Element structure Element nesting Element closing Element opening and closing tags Quotes for attributes Documents that aren't well-formed XML, HTML, and XHTML Understanding HTML How is XML different from HTML? Where does XHTML fit in? Understanding related recommendations Understanding DTDs and XML schemas Understanding XSL Summary
Chapter 2 GENERATING XML CONTENT Authoring XML documents in a text editor Using text and HTML editors Using XML editors Using Stylus Studio 2008 XML Working with Dreamweaver Generating XML content from a database Using a web server to generate XML content WorkingwithVB.NET Working with PHP Working with ColdFusion Generating XML from other software packages Getting started with XML in Excel 2007 and Word 2007 Generating XML from Word 2007 Creating an XML document in Word using Save As Creating XML content in Word by using a schema
CONTENTS Generating XML from Excel 2007 Generating an XML document in Excel using Save As Creating XML content in Excel using a schema Creating XML content with Access 2007 Validation and XML content in SWF applications Summary
56 56 57 59 61 62
Chapter 3 ACTIONSCRIPT 3.0 AND XML
65
Differences between ActionScript 2.0 and 3.0 XML as an ActionScript data type Writing XML inline within ActionScript Writing XML with the XML tag in Flex Overview of the new ActionScript 3.0 classes The ActionScript 3.0 XML class The XMLList class The XMLListCollection class The QName and Namespace classes Working with the XML class Properties of the XML class Working with XML properties in Flash Working with XML properties in Flex Methods of the XML class Locating XML content Instructions for the code samples Working with attributeO and attributesO Finding child elements Finding descendants Finding elements Finding the parent element Locating text Finding information about XML content Finding an object's position within its parent Determining content type Determining the number of elements Displaying the name of an element Determining the type of node Displaying a string representation of XML Modifying XML content Working with the XMLList class Working with the XMLListCollection class in Flex Setting up the Flex application Using a function to filter an XMLListCollection Sorting an XMLListCollection Understanding the Namespace class Understanding the QName class Limitations of working with the XML class Summary
Understanding E4X expressions Working through the examples Working with Flash Working with Flex Using the dot operator to specify a path Returning text Returning an XMLList Specifying an index Finding the last element Casting returned content Using the wildcard operator (*) Using the attribute operator (@) Looping through attributes Using the descendants operator (..) Working with filter expressions Working with equality Finding inequality Other comparisons Using AND and OR in conditions Using the additive operator (+) Including other ActionScript expressions Assigning values Simple assignment with = Compound assignment with += Deleting content E4X in action Flash example Flex example Summary
Chapter 5 USING THE URLLOADER CLASS WITH XML DOCUMENTS Using the URLLoader class Properties of the URLLoader class Methods of the URLLoader class Events of the URLLoader class Limits of the URLLoader class Putting it all together Creating a URLLoader object Making the request Sending variables with the request Tracking the progress of a request Receiving a response Detecting errors Working through examples Working in Flash Working in Flex X
CONTENTS Updating content with the URLLoader class Sending variables in a Flash application Sending variables in a Flex application Understanding Flash Player security Understanding security sandboxes Creating a cross-domain policy file Writing a cross-domain policy file Issues with the cross-domain policy file Proxying data locally Summary
Chapter 6 LOADING METHODS SPECIFIC TO FLEX Loading external content Using the tag Properties of the tag Methods of the tag Events of the tag Putting it all together Creating an HTTPService request Making the request Sending variables with the request Specifying a return type Specifying a request method Receiving a response Using the HTTPService class Properties, methods, and events of the HTTPService class Putting it all together Creating an HTTPService request Making the request Sending variables with the request Specifying a return type Specifying a request method Receiving a response Accessing loaded content Accessing the lastResult property directly Binding the lastResult property Working through an tag example Working through an HTTPService class example Passing variables with the request Using to send variables Sending variables with the HTTPService class Summary
Chapter 7 LOADING METHODS SPECIFIC TO FLASH Understanding the AS 2.0 data components Understanding the XMLConnector Displaying read-only XML content Displaying updatable XML data Configuring the XMLConnector Using the Component Inspector Creating a schema from an XML document Creating a schema by adding fields Understanding schema settings Triggering the XMLConnector component Testing for a loaded XML document Working through a loading example Binding XML data directly to Ul components Adding a binding Configuring the binding Working through a binding example Extending the binding example Using the DataSet component Creating bindings with a DataSet component Putting it all together Summary
Chapter 8 MODIFYING XML CONTENT WITH ACTIONSCRIPT 3.0 Setting up the examples Setting up the Flash examples Setting up the Flex examples Changing element and attribute values Adding, editing, and deleting XML content Using appendChildO Using prependChildO Copying a node Inserting a child node Editing content Using setChildrenO Deleting an element Modifying element names and namespaces Adding a namespace Removing a namespace Setting the namespace Changing the local element name Changing the qualified element name Working through a modification example Working in Flash Working in Flex Points to note about the example Summary
Chapter 9 COMMUNICATING WITH THE SERVER Sending data to the server Structuring the file path Sending the variables Choosing a method Choosing the format Working with the URLLoader class Sending variables with the URLLoader class Receiving a response Handling errors Working through a URLLoader class example Understanding the VB .NET page Understanding the PHP page Understanding the ColdFusion page Working through the Flash example Working through the Flex example Working with the element Sending variables with the element Receiving a response Handling errors Working through an element example Working with the HTTPService class in Flex Sending variables with the HTTPService class Receiving a response Handling errors Working through a HTTPService class example Choosing the Flex approach Summary
Chapter 10 CONSUMING WEB SERVICES WITH FLEX Understanding web services Understanding SOAP web services Understanding the role of WSDL Using Flex to consume a web service Working with the element Creating the web service request Specifying the operation Making the request Receiving the response Accessing the reply Understanding the resultFormat of an operation Handling errors Working through a tag-based example Working with the WebService class Properties of the WebService class Methods of the WebService class Events of the WebService class Understanding the Operation class
CONTENTS Properties of the Operation class Methods of the Operation class Events of the Operation class Consuming a web service with ActionScript Creating the web service request Specifying the operation Making the request Receiving the response Accessing the reply Understanding returned data types Handling errors Working through a scripted example Using Flex Builder to manage web services Working through the Web Service Introspection wizard Managing web services Consuming the web service Using MXML tags with the generated classes Scripting the generated classes Summary
Chapter 11 CONSUMING WEB SERVICES WITH FLASH Consuming web services with the URLLoader class Understanding the WSDL file Using GET to consume a web service Working through a GET example Consuming a web service with POST Working through a POST example Consuming a SOAP web service with the as3webservice extension Working through an as3webservice example Consuming a SOAP web service with the WebServiceConnector component Configuring the WebServiceConnector Adding parameters Determining the arguments for the operation Adding parameter bindings Triggering the web services call Binding the results Accessing the results in ActionScript Viewing the Web Services panel Working through a WebServiceConnector example Summary
Chapter 12 FLASH CASE STUDY Understanding Flickr Applying for a Flickr key Making a Flickr request
CONTENTS Understanding the Flickr API Understanding the returned photo XML document Understanding the returned people XML document Finding recent photos Finding interesting photos Searching for photos Finding owner information Receiving a Flickr response Receiving photo information Receiving person information Finding the URL of a photo Finding the page containing the photo Building the application Working through the interface Setting up the application Getting the recent photos list Displaying a large image and title Adding paging functionality Making cosmetic changes to the interface Viewing interesting photos Searching Flickr Showing owner information Summary
Chapter 13 FLEX CASE STUDY Understanding Adobe Kuler Applying for a Kuler key Understanding the Kuler feeds Accessing an existing feed Searching Kuler Receiving a Kuler response Building the application Working through the interface Setting up the application Creating the custom class file Getting the highest rated themes Displaying the theme Adding paging functionality Displaying the most popular schemes Searching Kuler Reviewing the completed code KulerLoader.as ColorSwatch.mxml KulerCompleted.mxml Summary
ABOUT THE AUTHOR Sas J a c o b s is a web developer and author who works with Flash, Flex, and XML. Sas has written several books on these topics and has spoken about them at conferences such as Flashforward, webDU, and FlashKit. Nowadays, Sas works as a software developer in the area of e-learning, where she tries to share her passion for all things ActionScript. When she's not working, Sas loves traveling, photography, running, and her son.
xvii
ABOUT THE TECHNICAL REVIEWER K e v i n Ruse is the principal of Kevin Ruse and Associates Inc., a web and print design and consulting firm based in Santa Clara, California. Kevin has been a trainer in web development and graphic design in a variety of environments, including De Anza Community College and the University of California, Santa Cruz. Kevin has also taught the staff and faculty at Stanford University and University of California, Berkeley. Kevin is an Adobe Certified Instructor and a Certified Training Partner for the Altova XML Suite of software and the XML editor. He currently teaches the following languages and software: Flex, Fireworks, Flash, Dreamweaver, Photoshop, InDesign, Acrobat, Quark XPress, JavaScript, ActionScript, MXML, XML, XSLT, DTD/Schema, ColdFusion, HTML, XHTML, and CSS. Kevin is the author of Web Standards Design Guide, a college textbook. He is an enthusiastic instructor who maintains a strong belief that with patience, determination, and guidance, all individuals can reach their maximum potential.
ABOUT THE COVER IMAGE DESIGNER C o r n é v a n D o o r e n designed the front cover image for this book. After taking a brief from friends of ED to create a new design for the Foundation series, he worked at combining technological and organic forms, with the results now appearing on this and other books' covers. Corné spent his childhood drawing on everything at hand and then began exploring the infinite world of multimedia, and his journey of discovery hasn't stopped since. His mantra has always been, "The only limit to multimedia is the imagination," a saying that keeps him moving forward constantly. Corné works for many international clients, writes features for multimedia magazines, reviews and tests software, authors multimedia studies, and works on many other friends of ED books. You can see more of his work at and contact him through his web site, www.cornevandooren.com. If you like Corné's work, be sure to check out his chapter in New Masters of Photoshop: Volume 2 (friends of ED, 2004).
xxi
INTRODUCTION This book started out as an update to my first book on Flash and XML. Originally, the idea was to update the content with the changes to XML in ActionScript 3.0. However, when it came to drafting the table of contents, I realized that there was a whole audience of Flex developers who would also benefit f r o m a book about XML and ActionScript 3.0. Hence, this book was born! So, my plan is for this book to cater to both audiences: Flash designer/developers and Flex developers. I've included common code approaches, as well as topics that are specific to each package. I've tried to show readers how to achieve the same XML results in both software packages. This book is best suited to people who have limited experience in the areas of XML and ActionScript 3.0. It is really pitched at introductory level users who are keen to learn more about ActionScript 3.0. The book is purposely simple in its approach, showing how to achieve common tasks required for working with XML in Flash and Flex. The Flash sections show function-based approaches, whereas the Flex sections show how to work with custom classes. I hope that you find this book useful and that it whets your appetite for working with XML in your SWF applications. Hopefully, you'll find that the power and simplicity of XML will inspire you in your Flash and Flex development efforts!
Layout conventions To keep this book as clear and easy to follow as possible, the following text conventions are used throughout: •
Important words or concepts are normally highlighted on the first appearance in italics.
•
Code is presented in f i x e d - w i d t h f o n t .
•
New or changed code is normally presented in bold fixed-width font.
•
Pseudo-code and variable input are written in italic fixed-width font.
•
Menu commands are written in the form Menu > Submenu > Submenu.
•
Where I want to draw your attention to something, I've highlighted it like this: C
\
Ahem, don't say I didn't warn you. • Sometimes code won't fit on a single line in a book. Where this happens, I use an arrow like this: This is a very, very long section of code that should be written all *»on the same line without a break. XXV
Chapter 1
INTRODUCTION TO XML If you work in the web design or development area, you've probably heard of XML. It's the basis of all modern web sites, but how many of us actually know what it really means? This chapter introduces XML and explains why it is such an important standard for exchanging information. I'll cover some of the basic concepts behind XML, including the rules governing the structure of XML documents. I'll also review some of the uses for XML and the reasons you might need to use it in your SWF applications. You'll see some examples of XML documents and, by the end of the chapter, have a solid understanding of XML and its related concepts. If you're already familiar with XML and are comfortable generating XML documents, feel free to skip forward to Chapter 3. If not, read on! You can download the resources referred to in this chapter from http://www.-Friendsofed.com.
What is XML? Let's start by answering the most basic question of all: what is XML? It's very difficult to provide a short answer to this question. The people who invented XML, the World Wide Web Consortium (W3C), provide the following definition for XML in their glossary at http://www.w3.org/TR/DOM-Level-2-Core/glossary.html.
1
CHAPTER 1 Extensible Markup Language (XML) is an extremely simple dialect of SGML. The goal is to enable generic SGML to be served, received, and processed on the Web in the way that is now possible with HTML. XML has been designed for ease of implementation and for interoperability with both SGML and HTML. I think the definition is very accurate if you already know about XML, but it doesn't really explain XML to a novice. Let's go back to basics and see what that definition really means.
Understanding XML XML describes a format that you can use to share information. By itself, XML doesn't do anything other than store information. It's not a programming language, so you can't use it to create stand-alone applications. XML simply describes information. XML documents need humans or software packages to process the information that they contain. XML stands for Extensible Markup Language, which is a little misleading, because XML is actually a metalanguage, so you can use it to create other markup languages. That's what the word extensible means in the acronym. The term markup means that the languages you create use tags to surround or mark up text, which you'll be familiar with if you write Extensible Hypertext Markup Language (XHTML). In fact, XHTML is one of the languages created by XML. We call XHTML a vocabulary of XML. It was created when HTML was redefined according to XML rules. For example, in XHTML, all tags must be in lowercase. This requirement doesn't apply to HTML. Think about the tags you use in XHTML, such as < p x / p > and < h l x / h l > . These tags mark up information that will display on a web page. You use these in a very specific way, according to some predefined rules. For example, one rule says that you can't include < p x / p > tags in the section of a web page. It's possible to make up your own XML vocabulary or work within a group to create an industry-wide language based on XML. By agreeing on an XML vocabulary, groups can share information using a common set of markup tags and structures. Chemical Markup Language (CML), for example, allows scientists to share molecular information in a standardized way. There are specific rules for structuring CML documents and referring to molecular information. MathML is another vocabulary of XML that describes mathematical operations. But you don't need to work with an existing vocabulary to include XML in your applications. It's possible—in fact, very likely—that you'll create your own XML structures to suit the particular needs of your application. You'll see this as we work through the examples in the book. So what type of information can you store in an XML document?
Storing information in XML documents XML documents work best with structured information, similar to what you would find in a database. Examples include lists of names and addresses, product catalogs, sales orders, an iTunes library— anything with a standardized format. Like a database, XML documents can show hierarchical relationships. Instead of breaking the information into tables and fields, elements and tags describe the data. By nesting tags inside each another, you can create a hierarchy of parent and child elements.
2
INTRODUCTION TO XML The following code block shows some sample XML elements. You can see the hierarchical relationship between the elements, and the tag names describe their contents. Sas Dacobs 123 Red Street, Redland, Australia 123 456 The code sample describes contact details, similar to what you would see in an address book. Most of us have an address book that we use to store contact information about our friends and colleagues. You might have the information in a software package like Outlook or Outlook Express. It might also exist on your mobile phone. Your address book contains many different names, but you store the same information about each contact: name, phone number, and address. The way the information is stored depends on the software package you've chosen. If the manufacturer changes the package or discontinues it, you'll need to find a new way to store information about your contacts. Transferring the information to a new software program is likely to be difficult. You'll need to export it from the first package, rearrange the contents to suit the second package, and then import the data. Most software applications don't share a standard format for contact data, although some can share information. You must rely on the standards created by each company. As an alternative, you could use an XML document to store the information. You could create your own tag names to describe the data. Tags like , , and would provide clear descriptions for your information. Any human who read the document would be able to understand what information the document held. Because the address book XML document has a standard format, you could use it to display your contacts on a web page. Web browsers contain an XML parser to process the XML content. You could also print out your contacts, or even build a SWF movie in Flash or Flex to display and manage your contacts. Your friends could agree on which tags to use and share their address books with each other. You could all save your contacts in the same place and use tags to determine who had contributed each entry. When you use a standardized structure for storage, you have endless choices about how to work with the information. So if XML is so useful, how did it all start?
XML, in the beginning XML has been around since 1998. It is based on Standard Generalized Markup Language (SGML), which was in turn was created out of Generalized Markup Language (GML) in the 1960s. XML is actually a simplified version of SGML. SGML describes how to write languages, specifically those that work with text in electronic documents. It is also an international standard: ISO 8879. SGML was actually one of the considerations for HTML when it was first developed.
3
CHAPTER 1 The first XML recommendation was released in February 1998. Since then, XML has increased in popularity and is now a worldwide standard for sharing information. Human beings, databases, and many popular software packages use XML documents to store and exchange information. Web services and RSS feeds also use an XML format to share content over the Internet. The W3C developed the XML specification. The W3C also works with other recommendations such as HTML, XHTML, and Cascading Style Sheets (CSS). Detailed information about the XML specification is available from the W3C's web site at http://www.w3c.org/XML/. The current specification is XML 1.1 (Second Edition), dated 16 August 2006. You can view this specification at h t t p : / / w w w . w 3 . o r g / TR/2006/REC-xmlll-20060816/. The W3C has also created a family of related recommendations that work together to create an independent framework for managing markup languages. The other areas covered by recommendations include XML schemas, which describe the structure and syntax of an XML document; XML stylesheets, which allow the transformation and output of XML content; and XPath, which describes how to navigate or locate specific parts of XML documents. When it created XML, the W3C published the following goals at http://www.w3.0rg/TR/REC-xml/ #sec-origin-goals: 1. XML shall be straightforwardly usable over the Internet. 2. XML shall support a wide variety of applications. 3. XML shall be compatible with SGML. 4. It shall be easy to write programs which process XML documents. 5. The number of optional features in XML is to be kept to the absolute minimum, ideally zero. 6. XML documents should be human-legible and reasonably clear. 7. The XML design should be prepared quickly. 8. The design of XML shall be formal and concise. 9. XML documents shall be easy to create. 10. Terseness in XML markup is of minimal importance. In other words, XML should be easy to use in a variety of settings, by both people and software applications. The rules for XML documents should also be clear so they are easy to create.
An XML example The following code block shows a simple XML document with address book data containing a single contact. If you've worked with XHTML, you'll see that the elements are written in a similar way. Sas Dacobs 123 Some Street, Some City, Some Country 123 456
4
INTRODUCTION TO XML The information is stored between tags, and the names of these tags are descriptive—for example, , , and . The casing of the opening and closing tags is consistent. The hierarchy within the document shows that the information relates to a single element. You can use any names for these elements, as long as you follow the rules for constructing XML documents. These rules are presented in the "Structuring XML documents" and "Understanding well-formed documents" sections later in the chapter. Now that you know a little more about XML, you may be wondering why it is so important and why might you want to use XML as a source of data.
Why XML? Quite simply, XML is simple, flexible, descriptive, accessible, independent, precise, and free! Let's look at each of these advantages of XML in turn.
Simple The rules for creating XML documents are very simple. You just need a text editor or another software package capable of generating XML formatted data. The only proviso is that you follow some basic rules so that the XML document is well-formed. You'll find out what this means a little later in the chapter, in the "Understanding well-formed documents" section. Reading an XML document is also simple. Tag names are normally descriptive, so you can figure out what data each element contains. The hierarchical structure of elements allows you to work out the relationships between each piece of information.
Flexible One key aspect of XML is its flexibility. As long as you follow some simple rules, you can structure an XML document in any way you like. The choice of tag names, attributes, and structures is completely flexible so you can tailor it to suit your data. You can also agree on an XML vocabulary so you can share information with other people. A Document Type Definition or schema describes the "grammar," or rules for the language. XML documents provide data for use in different applications. You can generate an XML document from a corporate software package, transform it to display on a web site using Extensible Stylesheet Language Transformations (XSLT), share it with staff on portable devices, use it to create PDF files with Extensible Stylesheet Formatting Objects (XSL-FO), and provide it to other software packages. You can reuse the same data in several different settings. The ability to repurpose information is one of XML's key strengths.
XSLT and XSL-FO are two related XML recommendations. Both of these recommendations describe how to change or transform an XML document into a different type of output. You might use an XSLT stylesheet to create HTML or text from an XML document. You could use XSL-FO to create a PDF document. V
J
5
CHAPTER 1 The way XML information displays is also flexible. You can display any XML document in any XML processor, perhaps a web browser, to see the structure of elements. You can also use the document to display the following: • A printed list of summary data • A web page displaying the full details of each element • A SWF movie that allows you to search the XML content
Descriptive Because you can choose your own tag names, an XML document becomes a description of your data. Some people call XML documents self-describing. The hierarchy of elements means that XML documents show relationships between information in a similar way to a database. For example, the hierarchies in the address book document tell us that each contact has a name, address, and phone number, and that we can store many different contacts.
Accessible XML documents separate data from presentation, so you can have access to the information without worrying about how it displays. This makes the data accessible to many different people, devices, and software packages. For example, the sample address book XML document could be accessed in the following ways: •
Read aloud by a screen reader
•
Displayed on a web site
•
Printed to a PDF file
•
Processed automatically by a software package
• Viewed on a mobile phone XML documents use Unicode for their standard character set, so you can write XML documents in any number of different languages. (The Unicode standard is maintained by the Unicode Consortium; see http://www.unicode.org/.) A SWF application could offer multilingual support simply by using different XML documents with equivalent content.
Independent XML is platform- and device-independent. It doesn't matter if you view the data on a PC, Macintosh, or handheld device. The data is still the same, and people can exchange it seamlessly. Programmers can also use XML to share information between software packages that otherwise couldn't easily communicate with each other. You don't need a specific software package to work with XML documents. You can type the content in just about any software package capable of receiving text. You can read the document in a web browser, text editor, or any other XML processor. XML documents can provide a text-based alternative to database content. In the case of web services, XML is an intermediary between you and someone else's database.
6
INTRODUCTION TO XML XML doesn't have "flavors" that are specific to a single web browser (like CSS), version, or operating system. You don't need to create three different versions of your XML document to handle different viewing conditions.
Precise XML is a precise standard. If you want your XML document to be read by an XML parser, it must be well-formed. Documents that aren't well-formed won't display. You're probably wondering what well-formed means. We'll cover that a little later, in the "Understanding well-formed documents" section. When a schema or Document Type Definition is included within an XML document, an XML processor can validate the content to make sure that the document structure conforms to the structural rules you've established. XML documents with schemas provide standards, so there is only one way that the data they contain can be structured and interpreted.
Free XML is a specification that isn't owned by any company or commercial enterprise. This means that it's free to use XML—you don't have to buy any special software or other technology. In fact, most major software packages either support XML or plan to support it in the future. So why should you use XML in your Flash and Flex projects?
Why is XML important in Flash and Flex? XML is an important tool for all web developers. Many people consider XML the lingua franca of the Internet as it provides the standard for data exchange among humans and machines in many different settings. An understanding of XML is essential for Flash and Flex developers for the following reasons: • XML is one way for Flash and Flex developers to store content that powers SWF movies. •
Flex uses a vocabulary of XML, called MXML, to describe interfaces.
• XML provides a mechanism for working with data that is disconnected from a database or the Internet. • ActionScript 3.0 contains features to make working with XML much easier than in previous versions, so it's a sound alternative to plain-text files for content.
XML as a SWF data source Storing content outside a SWF application means that clients can update their own content without needing to learn either Flash or Flex, or contact the developer each time they want to make a change. It's also possible to provide client tools that make it easy to generate the content automatically. Developers will understand the importance of storing information for SWF movies in an external data source. Doing so allows the content of a SWF application to change without the need to recompile it each time. Simply update the source document to update the information within the application.
7
CHAPTER 1 There are many possible sources of external data: text files, databases, and XML documents. While it's possible to create external text files for a SWF file, it's more practical to use an XML document, which can include the data and describe the hierarchical relationships between the data. Although a developer or client can create an XML document by hand, it's easier to generate the content automatically. With the assistance of a web server and a server-side language like PHP, Visual Basic .NET (VB .NET), or ColdFusion, databases can easily generate XML content suitable for a SWF application. You'll see how this can be done in the next chapter. In terms of security, it's good practice to use an XML layer between a user and database. Many software packages are capable of exporting their content in an XML format. The most recent versions of Microsoft Office allow you to save Word, Excel, and Access documents using either Microsoft's XML vocabularies or your own. Using standard business tools to generate XML content allows clients to take control of their own application content. For SWF applications that need to be portable, XML is an excellent choice as a data source. An XML document is usually small in file size, making it easy to include on a CD, DVD, or handheld device.
MXML in Flex In order to get the most out of Flex, developers need a good understanding of XML. Flex uses an XML vocabulary called MXML to describe application interfaces. MXML is a markup language that provides the same role in Flex applications as XHTML does in web pages. MXML consists of a set of tags that correspond to ActionScript 3.0 classes. Because MXML is a vocabulary of XML, it must follow the same rules and be well-formed. I'll cover this term in more detail later in the chapter.
ActionScript 3.0 and XML ActionScript 3.0 greatly simplifies the process of working with XML documents compared with earlier versions. XML is a native data type in this version of ActionScript, making it much easier to work with in both Flash and Flex. If you've worked with XML in an earlier version of ActionScript, you'll be used to writing complicated expressions and looping through collections of nodes to locate specific information in an XML document. The new process means that you can target content in an XML document by using element names instead. This change is significant and makes working with XML content much easier than in earlier versions of ActionScript. I have found that ActionScript 3.0 has saved me hours of development time when it comes to working with XML content. You'll find out more about the changes to ActionScript in Chapter 3. ActionScript 3.0 also includes a full implementation of the ECMAScript for XML (E4X) standard, ECMA-357 (see http://www.ecma-international.org/publications/-Files/ECMA-ST/Ecma-357.pdf). Because ActionScript 3.0 adheres to international standards, you can take advantage of any existing knowledge you have in this area. Learning E4X means that you'll be able to apply the same skills when working with JavaScript. Now that you appreciate why XML is important to Flash and Flex developers, let's look more closely inside an XML document.
8
INTRODUCTION TO XML
XML document sections It's important to understand exactly what the term XML document means. This term refers to a collection of content that meets XML construction rules. The document part of the term has a more general meaning than with software packages. In Flash or Flex, for example, a document is a physical file. While an XML document can be a physical file, it can also refer to a stream of information that doesn't exist in a physical sense. You can create these streams f r o m a database using a web server, and you'll see how this is done later in the book. As long as the information is structured according to XML rules, it qualifies as an XML document. An XML document is divided into two sections: the prolog and the content, or document tree. The content exists inside the document root or root element.
Document prolog The document prolog appears at the top of an XML document and contains information about the XML document as a whole. It must appear before the root element in the document. The prolog is a bit like the section of an XHTML document. The prolog consists of the following: •
An XML declaration
•
Processing instructions
•
Document Type Definitions (DTDs)
XML declaration The prolog usually starts with an XML declaration to indicate to humans and computers that the content is an XML document. This declaration is optional but if it is present, it must appear on the first line. At a minimum, the declaration appears as follows: The minimum information that must appear inside an XML declaration is an XML version. The preceding declaration uses version 1.0.
At the time of writing, the latest recommendation is XML 1.1. However, you should continue to use the version="l.0" attribute value for backward-compatibility with XML processors, unless you specifically need version 1. /. For example, XML 1.1 allows characters that can't be used in XML 1.0 and has slightly different requirements for namespaces.
The XML declaration can also include the encoding and standalone attributes. The order of these attributes is important. Encoding determines the character set for the XML document. You can use Unicode character sets UTF-8 and UTF-16 or ISO character sets like ISO 8859-1, Latin-1, or Western European. If no encoding attribute is included, it is assumed that the document uses UTF-8 encoding. Languages like Japanese
9
CHAPTER 1 and Chinese need UTF-16 encoding. Western European languages often use ISO 8859-1 to cope with diacritical characters, such as accent marks, that aren't part of the English language. The encoding attribute must appear after the v e r s i o n attribute. Here are some sample declarations that include an encoding attribute: The standalone attribute indicates whether the XML document uses external information, such as a DTD. A DTD specifies the rules about which elements and attributes to use in the XML document. It also provides information about the number of times each element can appear and whether an element is required or optional. The standalone attribute is optional. When it's included, it must appear as the last attribute in the declaration. The value standalone="no" can't be used when you are including an external DTD or stylesheet. Here is an XML declaration that includes this attribute:
Processing instructions The prolog can also include processing instructions (Pis). Processing instructions pass information about the XML document to other applications that may need that information in order to process the XML. Processing instructions start with the characters . You can add your own processing instructions or have them generated automatically by software packages. The first item in a processing instruction is a name, called the processing instruction target. Processing instruction names that start with xml are reserved. One common processing instruction is the inclusion of an external XSLT stylesheet. An XSLT stylesheet transforms the content of an XML document into a different structure, and I'll cover this topic in more detail later in this chapter, in the "Understanding XSL" section. A processing instruction that includes an XSLT stylesheet must appear before the document root. The following line shows how this processing instruction might be written: Processing instructions can also appear in other places in the XML document.
Document Type Definitions DTDs, or DOCTYPE declarations, can also appear in the prolog. A DTD provides rules about the structure of elements and attributes within the XML document. It explains which elements are legal in the XML document, and tells you which elements are required and which are optional. In other words, a DTD provides the rules for a valid XML document and explains how the document should be constructed.
10
INTRODUCTION TO XML The prolog can include a set of declarations about the XML document, a bit like an embedded CSS stylesheet in an XHTML document. The prolog can also include a reference to an external DTD as well as or instead of these declarations. The following shows an external reference to a DTD: All the other content in an XML document appears within the document tree.
Document tree Everything that isn't in the prolog is contained within the document tree. The tree contains all of the content within the document. The section "Structuring XML content" explains exactly what items appear here. The document tree starts with a document root or root element. An XML document can have only one root element. All of the content within the XML document must appear inside the document root. In HTML documents, the tag is the root element. This is a rule of a well-formed document.
Whitespace XML documents include whitespace so that humans can read them more easily. Whitespace refers to spaces, tabs, and returns that space out the content in the document. The XML specification allows you to include whitespace anywhere within an XML document except before the XML declaration. C
\ XML processors can interpret whitespace in an XML document, but ally display the spaces. If whitespace is important, there are ways processor to display the spaces using the xml: space attribute in an you to research that topic on your own if it's something you need to
many won't actuto force an XML element. I'll leave do.
Namespaces XML documents can get very complicated. One XML document can reference another XML document, and different rules may apply in each case. XML documents can also summarize content from multiple sources. For example, you might combine several different XML documents into one. It's possible that an XML document will contain elements that use the same name but that come f r o m different locations and have different meanings. For example, you might use the
element as part of an XHTML reference in a document about furniture, which also needs to use a
element as a description of the type of furniture. In order to overcome this problem, you can use namespaces to distinguish between elements. Namespaces associate each XML element with an owner to ensure it is unique within a document, even if there are other elements that use the same name. Each namespace includes a reference to a Uniform Resource Identifier (URI) as a way to ensure its uniqueness. A URI is an Internet address, and each URI must be unique in the XML document. The URIs used in an XML document don't need to point to anything, although they can.
11
CHAPTER 1 You can define a namespace using the xmlns attribute within an element. Each namespace usually has a prefix that you use to identify elements belonging to that namespace. You can use any prefix that you like, as long as it doesn't start with xml and doesn't include spaces. Here is an example of using a namespace:
Sas Dacobs In the < d e t a i l s > element, the FOE prefix refers to the namespace h t t p : / / w w w . f r i e n d s o f e d . c o m / n s / . You can also use this prefix with other elements and attributes to indicate that they are part of the same namespace, like this: 123 Some Street, Some City, Some Country This prefix indicates that the element also comes from the h t t p : / / w w w . f r i e n d s o f e d . c o m / ns/ namespace. You can also define a namespace without using a prefix. If you do this, the namespace will apply to all elements that don't have a prefix or namespace defined. It is referred to as the default namespace. In an XHTML document, the element includes a namespace without a prefix: The namespace then applies to all of the child elements of the element; in other words, all of the remaining elements in the XHTML document. It isn't compulsory to use namespaces in your XML documents, but it can be a good idea. Namespaces are also important when you start to work with schemas and stylesheets. ActionScript 3.0 provides mechanisms for working with namespaces when dealing with complex XML documents in SWF applications. You can find out more about namespaces by reading the latest recommendation at the W3C site. At the time of writing, this was the Namespaces in XML 1.1 (Second Edition) recommendation at h t t p : / / www.w3.org/TR/xml-namesl1/.
Structuring XML documents XML documents contain both information and markup. The information about the document appears in the prolog, as discussed in the previous section. You can divide markup into the following:
12
•
Elements
•
Attributes
•
Text
•
Entities
INTRODUCTION TO XML •
Comments
•
CDATA
Let's look at each of these items in turn, starting with elements.
Elements Each XML document contains one or more elements, and they will usually make up the bulk of the document. Elements, also called nodes, identify and mark up content. At the very minimum, an XML document will contain one element: the document root. Elements serve many functions in an XML document: •
Elements mark up content. The opening and closing tags surround text.
•
Tag names provide a description of the content they mark up. This gives you a clue about the purpose of the element.
•
Elements provide information about the order of data in an XML document.
•
The position of child elements can show their relative importance in the document.
•
Elements show the relationships between blocks of information. Like databases, they show how one piece of data relates to others.
Writing elements As in XHTML, XML tags start with a less-than sign (). The name of the tag appears between these signs: .
Although they are often used interchangeably, the terms element and tag have slightly different meanings. A tag looks like this: An element looks like this: Some text
v
An element usually contains both an opening tag and a closing tag, as well as the text node in between those tags.
If an element contains information or other elements, it will include both an opening and closing tag: . An empty element can be written using a single tag: . So, is equivalent to . (In XHTML, the and < b r / > tags are examples of empty elements.) As explained earlier, you can include whitespace anywhere within an XML document, so you can split elements across more than one line, as shown here: Some text
13
CHAPTER 1
Naming elements Element names must follow these rules: •
Element names can start with either a letter or the underscore character. They can't start with a number.
•
Element names can contain any letter or number, but they can't include spaces. In addition, there cannot be a space between the opening angle bracket (
> ' " &
&
Character entities replace reserved characters in XML documents. All tags start with a less-than sign, so it would be confusing to include another one in your code, like this: 3 < 5 To avoid causing an error during processing, replace the less-than sign with the entity & l t ; : 3 & l t ;
5
Some entities use Unicode numbers. You can use numbers to insert characters that you can't type on a keyboard or choose not to type because they conflict with an XML parser (such as Comments are a useful way to leave messages for other users of an XML document without affecting the way the XML document is processed. In fact, software that processes XML always ignores comments in XML documents.
17
CHAPTER 1 The following are the only requirements for comments in XML documents: •
A comment can't appear before the first line in an XML declaration.
•
Comments can't be nested or included within tag names.
•
You can't include - - > inside a comment.
•
Comments shouldn't split tags; that is, you shouldn't comment out just an opening or closing tag.
CDATA CDATA stands for character data. CDATA blocks mark text so that it isn't processed as XML. For example, you could use CDATA for information containing characters that would confuse an XML processor, such as < and >. Doing so means that any < or > character contained within the CDATA block won't be processed as part of a tag name. CDATA sections start with . The character data is contained within square brackets [ ] inside the section. 0 ]]> Entities will display literally in a CDATA section, so you shouldn't include them. For example, if you add & l t ; to your CDATA block, it will display the same way when the XML document is processed, rather than as a left-angle bracket character. The end of a CDATA section is marked with the ] ] > characters, so you can't include these inside a CDATA block.
A simple XML document So far, I've explained the structure and contents of XML documents. Now it's time to put this knowledge into practice to create a complete XML document. The following listing (provided as the address.xml file with this chapter's resources) shows a simple XML document based on the address book example introduced earlier in the chapter. I'll use this example throughout the rest of the chapter. Sas Dacobs 123 Red Street, Redland, Australia 123 456
18
INTRODUCTION TO XML Dohn Smith 4 Green Street, Lost City, Greenland 456 789 The first line declares the document as an XML document with UTF-8 encoding. The declaration is not required, but it's good practice to include it on the first line. A software package that opens the file will immediately identify it as an XML document. The remaining lines of the XML document contain elements. The first element , the document root, contains the other elements , , , and . There is a hierarchical relationship between these elements. There are two elements. They share the same parent and are child nodes of that element. They are also siblings to each other. The document uniquely identifies each element using an id attribute. The element contains the , , and elements, and they are child elements of the tag. The , , and elements are grandchildren of the element. The last line of the document is a closing tag, written with exactly the same capitalization as the first tag. In this document tree, the trunk of the tree is the tag. Branching out f r o m that element are the elements. Each element has , , and branches. Figure 1-1 shows the relationships between the elements in the phone book XML document.
phoneBook
contact
contact
id (attribute)
name
i
address
id (attribute)
i phone
name
address
phone
Figure 1-1. The elements within the phone book XML document
19
CHAPTER 1 In this example, I've created my own element names. These names are descriptive, so it's easy to figure out what I'm describing. If I want to share the rules for my phone book XML document with other people, I can create a DTD or XML schema to describe how to use the elements. I can view this, or any other XML document, by opening it in a web browser, as browsers contain XML processors. Figure 1-2 shows the address.xml file displayed in Mozilla Firefox.
Mozilla Firefox File
Edit
-
View
-
History
@
12 Customize Links
__ Bookmarks
£
LJ
Tools
Help
file:///E:/aip
-
Q Free Hotmail E Windows Media
Is»
| C | '
PSj
e
Q Windows
This XML file does not appear to have any style information associated with it. The document tree is shown below.
— — Sas Jacobs
INTRODUCTION TO XML See the earlier section on "Entities" for more information about which character entities you can use in an XML document.
Documents that aren't well-formed If you try to work with an XML document that is not well-formed, the XML processor will not be able to parse the document and will generate an error. For example, opening a document that isn't well-formed in a web browser causes the error shown in Figure 1-3.
X M L Parsing Error: mismatched tag. Expected: . Location: fiie:///E:/aip/ciients/Apress-FriendsOfEd/XML%20ar d< Line Number 4, Column 23: Sas
<
Jacobs A H .
I
>
http: //video. google. com. au/?hl =en&tab =wv
Figure 1-3. Firefox displays an error when you try to open a document that isn't well-formed. In this case, the error message shows that the closing element name was expected. You can test this with the file address_not_well_-Formed.xml, included with this chapter's resources. Try opening it in a web browser to see the error. You'll also see an error if you try to work with a document that isn't well-formed in a Flash or Flex application. As long as you follow the rules outlined in this section when creating XML content, you'll generate well-formed XML documents.
The term well-formed doesn't have the same meaning as the term valid when applied to an XML document. A well-formed document follows the rules outlined in this section. A valid document is one that is constructed correctly according to the rules contained within an XML schema or DTD. It is possible for a document to be both well-formed and invalid.
23
CHAPTER 1
XML, HTML, and XHTML The terms XML, HTML, and XHTML sound similar, but they're really quite different. XML and HTML are not competing languages. They complement each other as technologies for managing and displaying online information. XML doesn't aim to replace HTML as the language for web pages. XHTML is a hybrid of the two languages, with more robust construction rules than HTML.
Understanding HTML HTML was designed as a tool for sharing information online in web pages. It defines a set of tags that describe structure and web site content. HTML has been around for a long time. When it was first created, HTML didn't have very robust construction rules. In fact, the rules for working with HTML are a little inconsistent. Some tags, such as , must be closed; others, like and
, do not need to be closed. Because of these inconsistencies, it is possible to include major errors in an HTML document and still have the page display correctly in a web browser. For example, in many browsers, you can include two < t i t l e > tags, and the page will still load. You can also forget to include a closing
tag, and the table will still be rendered in many browsers. The HTML language includes formatting instructions along with the information. There is no separation of the presentation from the structure and content in a document. HTML is supposed to be a standard, but in the past, it often worked differently across web browsers. Most web developers knew about the problem, and they created different web sites so that they appeared the same way in Internet Explorer, Opera, Firefox, and Netscape for both PCs and Macs. Like XML, HTML comes from SGML. Unlike XML, HTML is not extensible. You're stuck with a standard set of tags that you can't change or extend in any way.
How is XML different from HTML? Unlike HTML, XML deals only with content. XML describes the structure of information, without concerning itself with the appearance of that information. Using XML, you can create any element to describe the structure of your data. An XML document can show relationships in your data, just as a database can. This just isn't possible in an HTML document, as the HTML tags don't imply any data relationships. They simply deal with structure and formatting. XML documents don't deal with the display of information. If you need to change the way an XML document appears, you can change the appearance by using CSS or Extensible Stylesheet Language (XSL). XSL transformations can also reorganize, sort, or filter XML content. You can even use them to create XHTML from an XML document, or to sort or filter a list of XML elements. XML content is probably easier to understand than HTML content. The names of tags normally describe the data they mark up. In the sample address.xml file, tag names such as and describe exactly what data each element contains.
24
INTRODUCTION TO XML You can use an XML structure to display information directly in a web page. However, it's more likely that you'll use the XML document behind the scenes as a data source. It can provide the content for a web application or a SWF movie. Compared with HTML, XML is much stricter when it comes to constructing markup. There are rules about how to write tags, and you've seen that all XML documents must be well-formed before they can be processed. A DTD or XML schema can also provide extra rules for the way that elements are used. These documents determine whether XML content is valid according to the rules of the vocabulary. The rules for construction can include the legal names for tags and attributes, whether they're required or optional, as well as the number of times that each element must appear. In addition, schémas specify what data type must be used for each element and attribute.
Where does XHTML fit in? XHTML evolved so that the useful features of XML could be applied to HTML. The XHTML specification became a recommendation in 2000 and was revised in 2002 (see http://www.wB.org/TR/xhtmll/). The W3C says that XML reformulated HTML into XHTML. XHTML documents have much stricter construction rules and are generally more robust than their HTML counterparts. The HTML specification provides a list of legal elements and attributes within XHTML. XML governs the way that the elements are used in documents. Together, they merge to form XHTML. One example of this merging is that the HTML tag must be rewritten as or < b r x / b r > . In XHTML, web designers can't use a single
tag to create a paragraph break, as is permissible in HTML. Another difference between HTML and XHTML is that you must write attribute values in full. For example, the following HTML is acceptable: In XHTML, this must be written as follows: The following list summarizes the main changes from HTML to XHTML: • You should include a DOCTYPE declaration specifying that the document is an XHTML document. • You can optionally include an XML declaration. • You should include a namespace declaration in the opening element. • You must write all tags in lowercase. • All elements must be closed. • All attributes must be enclosed in quotation marks. • All tags must be correctly nested. • The id attribute should be used instead of name. • Attributes can't be minimized.
25
CHAPTER 1 It's no accident that some of these changes are the same as the rules for well-formed XML documents. The following listing shows the sample address.xml document rewritten as an XHTML document:
Sas Dacobs
123 Red S t r e e t , Redland, A u s t r a l i a < / t d >
12B 456
Dohn Smith
4 Green S t r e e t ,
456 789
Lost C i t y , Greenland
Notice that the file includes both an XML declaration and a DOCTYPE declaration, as well as a namespace in the element. You can see the content in the address.htm file included with this chapter's resources. You're probably used to seeing information like this in web pages. A table displays the content and lists each contact in a separate row. Figure 1-4 shows how this document appears in Firefox.
UILUÖ o
vJL Mozilla Firefox File
Edit *
View
History
* iP3
Customize Links
Bookmarks
Tools
Help
Q file:///E:/aip/dienfe/i | ' |
[j Free Hotmail E Windows Media
Q Windows
Ek-
Google
»
Sas Jacobs 123 Red Street, Redland Australia 123 456 John Smith 4 Green Street, Lost City. Greenland 456 789 Done
Figure 1-4. An XHTML file displayed in Firefox
26
•lai.ü
INTRODUCTION TO XML I've rewritten the content in XHTML so it conforms to the stricter rules for XML documents. However, the way the document is constructed may still cause some problems. Each piece of information about my contacts is stored in a separate cell within a table. The
tags don't give me any clue about what the cell contains. I get a better idea when I open the page in a web browser. Because this web page is essentially an XML document, I could use it as a data source for a Flash or Flex project. However, in this example, the addresses appear inside generic
tags that indicate the document structure. The tags don't specifically label each piece of data and indicate their relationships. I can't easily identify which column contains the names and which contains the phone numbers. The XHTML document doesn't inexorably link the name information with the first column of the table. If I used Flash or Flex to extract the content, I would experience difficulties if the order of the columns changed. XHTML controls the way the information is structured on the page. I can make some minor visual adjustments to the table using stylesheets, but I can't completely transform the display. For example, I can't remove the table and create a vertical listing of all entries without completely rewriting the XHTML source. Each time I print the document, it will look the same. I can't exclude information such as the address column from my printout. I don't have any way to filter or sort the information. I am not able to extract a list of contacts in a specific area or sort into contact name order. Compare this case with storing the information in an XML document. I can create my own tag names and write a schema that describes how to use these tags. When I view the document in a web browser, the tag names make it very clear what information they're storing. I can apply an XSL transformation to change the appearance of the document or to sort or filter the contents. I can display the document as a table or as a bulleted list. I can sort the items into name order or by phone number. I can even filter the contents so that only a subset of contacts appears. XML isn't a replacement for XHTML documents, but it certainly provides much more flexibility for working with data. You're likely to use XML documents differently from how you use XHTML documents. XML documents are a way to store structured data that may or may not end up in a web page. You normally use XHTML only to display content in a web browser.
Understanding related recommendations As you saw earlier in the chapter, XML works with other recommendations from the W3C that provide additional functionality. Although there are many other recommendations, two areas are of particular importance: DTDs/XML schemas and XSLT. Let's take a brief look at both, starting with DTDs and XML schemas.
Understanding DTDs and XML schemas DTDs and schemas perform the same role: they describe the rules for a vocabulary of XML. They provide information about the element names, their attributes, how many times they can appear and in what order, and what type of data they must contain.
27
CHAPTER 1 f
\
In addition to the two approaches discussed here, there are other ways to describe valid XML documents. You can use alternative schema languages, such as Relax NG and Schematron. However, these approaches are not recommendations from the W3C.
If you have either a DTD or an XML schema, you're able to determine whether an XML document is valid according to its construction rules. This process is called validation and is usually carried out automatically by a software package. An XML schema, sometimes called an XML Schema Definition (XSD) file, uses an XML format to describe these rules. The W3C created this vocabulary of XML. A DTD is an older method and uses a different method for writing validity rules. HTML and XHTML files include a DTD declaration at the top of the file to indicate which version of the language they use. This declaration allows a validator to determine whether the web page is valid. The following code listing shows a simple XML schema that describes the rules for the XML vocabulary used in the address.xml file. You can find this file saved as addressSchema.xsd with the chapter resources. It's easy to understand the rules for this language f r o m this schema document. To start with, the schema uses an XML structure and is a well-formed document. The schema describes a element that is a complexType element, meaning that it contains other elements. The attribute value unbounded means that it can contain any number of elements.
28
INTRODUCTION TO XML The element contains three other elements—, , and —each of which dictates that the contents of the element should be a string. The element also has a required attribute called i d . Of course, there is more to the language than this simple file, but it gives you a starting point for understanding XML schemas.
Understanding XSL Another important recommendation from the W3C is that for XSL. This recommendation covers two areas: XSLT stylesheets and XSL-FO. An XSLT stylesheet transforms one XML document—the source document—into a second type of document, called the transformed document. You might use an XSLT stylesheet, for example, to convert an XML document into an XHTML document for display in a web browser. XSL-FO stylesheets specify output for an XML document and might be used, for example, to output the content as a styled PDF file. An understanding of XSLT is important because it allows XML documents to be rewritten for different purposes. It could allow XML content from one database to be transformed to a different structure, suitable for import into another software package. XSLT transformations take place because of the rules written into an XSLT stylesheet. Unlike CSS, an XSLT stylesheet doesn't deal with the formatting of content. Rather, it can change element names and structures, perform simple calculations, and even sort and filter the content in the source document. The following code listing shows an XSLT stylesheet that transforms the address.xml file into an XHTML page, displaying the contacts in an unordered list. You can find the XSLT stylesheet saved as l i s t S t y l e . x s l with the other chapter resources. Phone Book
This stylesheet creates some simple HTML code and creates one < l i > element for each element in the source XML document. Again, there's a lot more to this recommendation than this simple example shows, but it does give you an idea of how XSL works.
29
CHAPTER 1
Summary In this chapter, you learned about XML and the contents of XML documents. You also learned about the differences between XML, HTML, and XHTML. You should now understand the advantages of working with XML as a data source for your SWF applications. The importance of XML cannot be overstated. It allows organizations and individuals to create their own mechanisms for sharing information. At its most simple, XML provides a structured, text-based alternative to a database. More complex uses of XML might involve data interactions between corporate systems and outside consumers of information. The most important thing to remember is that an XML document can provide a data source for many different applications. The widespread adoption of XML by major software companies such as Microsoft and Adobe ensure its future. Most of the popular database packages provide XML support. If it's not already there, you can expect XML to be part of most software packages in the near future. In the next chapter, we'll look at some of the different ways to generate XML content for use in Flash and Flex applications. You'll see the role of server-side languages like PHP, ASP .NET, and ColdFusion in creating content from databases. I'll also demonstrate how you can create XML content from Office 2007 software packages.
30
Chapter 2
GENERATING XML CONTENT Before you can start working with XML content in Flash and Flex applications, you'll need to create the XML documents that you want to use. These can be stand-alone physical XML files or content from a database. Remember that an XML document may simply be a stream of information generated electronically. The easiest way to create XML content is to save a text file with an .xml extension. You can also use a web server to create either a physical XML document or a stream of XML information from a database. You can even generate the content from existing data in another software package such as Microsoft Office. In this chapter, I'll cover all of these possibilities. I'll show you how to write your own XML content by hand and demonstrate the use of an XML editor. I'll also explain how to automate the process of creating XML documents by using a web server, some server-side processing, and a database. We'll do this with ASP .NET, PHP, and ColdFusion. I'll finish by giving you an overview of generating content from Microsoft Office 2007. Each of the XML documents that you create for your SWF applications is likely to have unique content and to use a different structure. These documents may use an existing vocabulary of XML, or you may decide on your own tag names. The only thing that your XML documents will have in common is the rules that you use to create them. At the very minimum, all XML documents must be well-formed, a concept covered in the first chapter of this book.
33
CHAPTER 1 You can download the resources used for this chapter from http://www.-Friendsofed.com. We'll start by looking at how you can use a text editor to create physical XML files.
Authoring XML documents in a text editor There are many different approaches that you can use to author physical XML files. For example, you can use a text editor like Notepad or SimpleText. You can also use an HTML editor like Adobe HomeSite or an XML editor such as Stylus Studio 2008 XML. You can even work with a web design tool like Dreamweaver. We'll start with a look at how you might use a text editor to write a physical XML document.
Using text and HTML editors You can use a text editor to create a physical XML file by writing every line in the software package using your keyboard. Text editors provide no automation, so this process could take a long time if you're working with a large document. If you're working with an existing XML vocabulary, you'll need to be familiar with the language-construction rules so that you use the proper element names in the correct way. As you learned in Chapter 1, an XML schema or a DTD describes these rules, and the process of checking is called validation. If you are creating your own XML elements to describe the data, you can just type the tag names and structures as you need them. You'll need to keep track of the structure and element names yourself. When you've finished entering the content, you can then save the file with the extension .xml. Text editors are easy to use and either free or very cheap. Their main problem is that they don't offer any special functionality for XML content. Text editors don't help you identify documents that are not well-formed. They don't provide error messages if tag names don't match, if you've mixed up the cases of your element names, or if you've nested elements incorrectly. Many text editors don't automatically add code-coloring to the elements in your document so you can visually check your markup. In addition, text editors don't include XML-specific tools. So, if you're using an existing XML vocabulary, you can't automatically check your XML document against a DTD or XML schema to make sure that it is valid according to the rules of that vocabulary. A text editor can't apply an XSLT stylesheet transformation to change the output of an XML document. In fact, you may not find any errors in your XML documents until you first try to work with the document in another application, and that's usually too late! Flex Builder is an example of a text editor that allows you to work with XML files. You can use it to create and edit an XML document. However, unlike when you're working with MXML documents, Flex Builder doesn't add coloring of tags in standard XML documents. Also, it also doesn't include XML-specific tools other than those specifically designed for working with MXML. As an alternative, you can use an HTML editor like Adobe HomeSite (http://www.adobe.com/products/ homesite/) or AceHTML (http://so-Ftware.visicommedia.com/en/products/acehtml-Freeware/) to create XML documents. One advantage of these software packages over text editors is that they can
34
GENERATING XML CONTENT automate the process a little. They add coloring to opening and closing tags to make it easier to read your content. Some also add the XML declaration automatically. HTML editors often come with extensions for working specifically with XML documents. These might add the correct declarations to the file and auto-complete your tag names. As with a text editor, you'll still need to type in most of your content line by line. Most HTML editors don't include tools to validate content and to apply transformations. You can expect that functionality only from an XML editor.
Using XML editors An XML editor is a software program designed to work specifically with XML documents. You normally use an XML editor to enter your XML content manually, similar to the way that you use a text or HTML editor. Most XML editors include tools that auto-complete tags, check for well-formedness, validate XML documents, and apply XSLT transformations. In addition to creating XML documents, you can use XML editors to create XSLT stylesheets, DTDs, and XML schemas. You're not limited to working with only XML-structured content. It isn't mandatory to use an XML editor when creating XML documents, but it's likely to save you time, especially if you work with long documents. XML editors include both free and "for purchase" software packages. If you plan on spending time creating physical XML documents by hand, I recommend that you invest the time to learn more about an XML editor. Common XML editors include the following: • Stylus Studio 2008 X M L (http://www.stylusstudio.com/) • Altova X M L S p y 2008 (http://www.altova.com/products/xmlspy/xml_editor.html) • XMLBlueprintXML Editor (http://www.xmlblueprint.com/) • (http://www.oxygenxml.com/) • Microsoft X M L Notepad (http://www.codeplex.com/xmlnotepad) • XMLEditPro (freeware) (http://www.daveswebsite.eom/software/xmleditpro.2.0/) • X M L F o x (freeware) (http://www.xmlfox.com/) To give you an idea of the advantages of using an XML editor, I'll show you how Stylus Studio 2008 XML works.
Using Stylus Studio 2008 XML Stylus Studio 2008 XML is a full-featured XML editor that you can try before buying. It allows you to create and edit XML, XSLT, and XML schema documents. It can check that a document is well-formed, validate an XML document against an XML schema or DTD, and apply an XSLT transformation. In this section, we'll look at some of the features of Stylus Studio 2008 XML as an illustration of what's possible with an XML editor. If you want to follow along, download it and install it on your computer. It's a PC-based tool, and your PC will need to have the Java Virtual Machine installed. You can download a seven-day trial version of the software. Those of you working on a Macintosh will need to get hold of a PC if you want to try out the features shown here. Figure 2-1 shows the Stylus Studio 2008 XML window. If you've worked with Flex Builder, you'll find parts of the layout familiar. On the left side of the window, you can see a list of all projects. Projects
35
CHAPTER 1 allow you to work with a set of related files, and they are really an organizational tool. Stylus Studio 2008 XML includes an Examples project containing many different types of sample documents.
Figure 2-1. The Stylus Studio 2008 XML window The right side of the window contains the File Explorer, which allows you to locate files on your computer or by FTP. You can drag files from the File Explorer on the right to a project on the left side of the window, or you can double-click to open a file that isn't part of a project. The middle of the screen contains any documents that are open. In Figure 2-1, you can see the address.xml file. We used this file in Chapter 1, and you can find it with the Chapter 2 resource files. Notice that Stylus Studio 2008 XML automatically adds coloring to the content to make it easier to view the elements and attributes in the document. You can collapse and expand elements by clicking the minus and plus signs to the left of the document. 0 Stylus Studio 2008 XML H o m e Edition i File Edit View
Project
Debug
Source!
ID B g [ 9 DTD Schema J E Java: Tent Editor XML Document 1 1 XML Schema XSIT
-
X
XSLT Stylesheet
Figure 2-2. Options available when creating a new document with Stylus Studio 2008 XML
36
When you create a new document, Stylus Studio 2008 XML allows you to choose f r o m a range of different document types. You can create a standard XML document, an XML schema or DTD, or even a stylesheet, as shown in Figure 2-2. If you choose to create a standard XML document containing data, the software automatically adds the following XML declaration at the top of the file:
GENERATING XML CONTENT You can assign an XML schema, DTD, or XSLT stylesheet to the document through the XML menu. Stylus Studio 2008 XML will then add the relevant directives to the file. You can use Stylus Studio 2008 XML like a text editor in Text view, which you can see in Figure 2-1. In Tree view, it shows the document structures graphically. You can see the address.xml file displayed in Tree view in Figure 2-3. Notice that I've expanded all of the elements in the file.
ItMt address jonl |
K
X d
Ü
w s 1I
Tag % m
x tm a
Value
H-"i3 phaneBoofe: E *•5 c o n t a c t
0 id *t"5 n a m e
1
"I" a d d r e s s
123 Red Street,
Sas Jacobs
E
*• 5
contact Q
Redland,
Australia
123 456
phone
2
id
J o h n Sruith
"x^ n a m e address
4 Green Street,
phone
456 789
Lost City,
Greenland
Figure 2-3. The address.xml document in Tree view Tree view removes the markup f r o m the document and displays the content in a hierarchy of tags, showing the values on the right of the screen. This layout makes it easy to determine the document structure. You can collapse and expand each area with the plus and minus signs to the left. Ifyou open an XML document that has an associated XML schema or DTD, you'll be able to see the details in Schema view. I've done this with the document addressSchema.xml, as shown in Figure 2-4. This file contains the same markup as in the address.xml file but includes a reference to the addressSchema. xsd file. You can find both files with your resources for this chapter.
.,-,„ EarpleVdM xd a n videusDelxjyjtsJ _ Ur sorrpteZVideoÄSl B tü XS LFofmattnqOtyectE
|lype
xscl:Bering
^address Tvpc xad:ctrine "{Jj phnii; Type xad:string
IMlrilcKJIsI
B
•«
ndiIrcíiíiHkIipm . x.id
Le ti mnmol caatogjol SVG >>,i rhnrt xrrJ )5u chaitxsl
Properties Homo ID [Wrravr Typ« DefaJt Fixed Value Form Min Occur. Mnr O r x j r NilljAiltr ¿fcäract Blocked Siijet
It In Sync
Value Validate Document. If the XML document is valid according to the XML schema, Stylus Studio 2008 XML will report this with the message The XML document address.xml is valid in the output section at the bottom of the window. If the document is not valid, you'll see an error message relating to the invalid content. I haven't provided an example here, but you might want to try out validity checking with the addressSchema. xml file. Try adding extra elements out of sequence or deleting one of the required elements. Finally, if you're going to transform your XML document with an XSLT stylesheet, you can use Stylus Studio 2008 XML to create the stylesheet as well as to preview the transformation before it is applied. You can associate an XML document with a stylesheet by choosing XML > Associate XML With XSLT Stylesheet. This action will add a stylesheet reference at the top of the XML document. You can see an example saved in the resource file addressStylesheet.xml. This XML document references the XSLT stylesheet l i s t S t y l e . x s l , which is also included with the chapter resources. The transformation in the file displays the contents as an XHTML list. Once you've added a stylesheet reference to your XML document, choose XML > Preview in Internet Explorer. Stylus Studio 2008 XML will apply the transformation and provide a preview of the transformed output. You can see the transformation provided by l i s t S t y l e . x s l in Figure 2-8.
* e=-Altso n-=Jauth orFirstN a rre:=-:authorLastNa me>Ambrose Shppping for profit and pteasure-=:/t>o• kName> < bo • kPu blish Year>2002-ookPu bltshYear:14.9&*tf>ookCost>
Figure 4-2. The TextArea displays the contents of the authorsXML object. 10. If you look at the XML structure, you'll see that all authors appear inside the element. You want to display the author's first and last names in the ComboBox, so you need to set the element as the data provider for this control. Add the following lines before the closing brace of the i n i t A p p ( ) function: authorList = new XML(authorsXML.authors); author_cbo.dataProvider = new DataProvider(authorList); The first new line populates the a u t h o r L i s t object using the E4X expression authorsXML. authors. This expression returns an XMLList of all elements. There is only one of these elements, so you're able to cast it to the type of an XML object using the XML() constructor method. You can then create a DataProvider object from the a u t h o r L i s t XML object and set it as the dataProvider property for the author_cbo instance. Because you're working with an XML object, the data provider has access to all of the child elements of the root element. 11. Because there are multiple child elements, you need to specify which to use for the ComboBox label. You could assign a single element or use the l a b e l F u n c t i o n property of the ComboBox. Because you want to display both the author's first and last name, you'll need to create a l a b e l F u n c t i o n that joins these values and adds a space in between. Name this function getFullNameQ. Add the following line underneath the l a b e l F u n c t i o n property of the ComboBox.
data
provider
assignment
line.
It
sets
the
author_cbo.labelFunction = getFullName;
123
CHAPTER 6 12. You now need to create the getFullName() function. Add the new function, shown here, to the bottom of the actions layer: function getFullName(item:Object):String{ return item.authorFirstName + " " + item.authorLastName;
} The label function receives an Object called item as a parameter. This Object contains the data item f r o m the data provider. The getFullName() function finds the authorFirstName and authorLastName properties of this Object and concatenates them with a space in between to create the full name. 13. Test the movie again. Figure 4-3 shows how the ComboBox should appear, with the names of four authors. If you can see all of the names, continue with the next steps. If not, you'll need to recheckyour steps to this point to make sure that you've carried them out correctly. Author Explorer Author name
Choose author
| \
Alison Ambrose Books
Douglas Donaldson Lucinda Larcombe Saul Sorenson
Figure 4-3. Populating the ComboBox control 14. When the author selection changes, the List control should show all books associated with that author. The ComboBox needs a change handler to deal with this action, so add the following line to the i n i t A p p ( ) function: author_cbo.addEvent Listener(Event.CHANGE, changeHandler); This line assigns a change handler function called changeHandler. It's common practice to name the handler functions in this way so it's easy to determine which event they manage. 15. Now you need to create the function that will be called when the ComboBox selection changes. Add the following changeHandler() function at the bottom of the actions layer: function changeHandler(e:Event):void { showBooks(e.currentTarget.selectedlndex); } The function calls the showBooks() function, which you haven't created yet, passing the s e l e c t e d l n d e x property of the ComboBox. The number is zero-based, so it will tell you exactly which element has been selected f r o m the data provider. You find the ComboBox control using the c u r r e n t T a r g e t property of the event passed with the changeHandler() function. You could also use the ActionScript expression author_cbo. s e l e c t e d l n d e x to achieve the same result.
124
USING E4X EXPRESSIONS 16. The next step is to create the showBooksQ function. Remember that it receives the selected index in the ComboBox from the changeHandler() function. Add the showBooksQ function to the bottom of the actions layer. function showBooks(authorIndex:int):void{ bookList = new XML(authorsXML..author[authorIndex].books); booksJList.dataProvider = new DataProvider(bookList); XMLjtxt.text = bookList.toXMLString();
}
The showBooksQ function starts by identifying the list of all of the selected author's books using the E4X expression authorsXML. . a u t h o r [ a u t h o r I n d e x ] .books. In other words, find any descendants with the same index as the selected index of the ComboBox and return all of that author's books. The function creates a new XML object and populates it with the XMLList returned f r o m the E4X expression. You can assign the value in this way because the E4X expression returns an XMLList containing a single element, , which becomes the root element of the new XML object. The showBooksQ function sets the new XML object as the d a t a P r o v i d e r property for the List component, by creating a new DataProvider object. It finishes by displaying the XML object in the TextArea so you can check what has been returned. 17. The last task is to display the name of the book in the List control. If you tested the movie now, you would see the correct number of items in the List control, but there wouldn't be any associated labels—only blank lines would appear. You need to add a label function for the List to the i n i t A p p Q function, at the bottom of the actions layer, as follows: booksJList.labelFunction = getBookName; This label function is named getBookName(). It works in the same way as the getFullName() function described earlier. function getBookName(item:Object):String { return item.bookName;
} I've included the complete code f r o m the actions layer next. I've removed the XML content except for the first and last lines for brevity. In your version, you should see the entire XML object. import fl.data.DataProvider; var authorsXML:XML; var authorList:XML; var bookList:XML; initAppQ; stopQ; function initAppQ:void { authorsXML = //remaining XML content appears between these lines ;
} function getFullName(item:Object):String{ return item.authorFirstName + " " + item.authorLastName;
}
function getBookName(item:Object)¡String { return item.bookName;
}
function changeHandler(e:Event):void { showBooks(e.currentTarget.selectedlndex);
}
function showBooks(authorIndex:int):void{ bookList = new XML(authorsXML..author[authorIndex].books); booksJList.dataProvider = new DataProvider(bookList); XML_txt.text = bookList.toXMLString();
}
Test the SWF file now. Choose an author f r o m the ComboBox, and you should see the List component populate with the book titles f r o m that author, as shown in Figure 4-4.
I authorExample.swf File
View
Control
Debug
Author Explorer Author name
[ Lucinda Larcombe
5J
Books Bikes as an alternative source of transport Entertaining ways Growing radishes Growing tulips
XML Bikes as an alternative source oftransport 2QQ4 32.6G Entertain in g w ays 200(k/bookPu blish Yea r> 49.5Ck/bookCost> Grow in g rad ishes
Figure 4-4. The completed Flash application
126
USING E4X EXPRESSIONS In this example, you used a static XML object to populate a ComboBox control and a List control. As I mentioned earlier, you would normally load this content f r o m an external file, and I'll show you that approach in the next chapter. You can find the completed file saved as a u t h o r E x a m p l e . f l a with the other chapter resources. If you're feeling adventurous, you may want to extend the example by displaying the year of publication and book cost as well. You may also wish to add some styling to improve the appearance of the interface! Let's see how we might approach the same application in Flex.
Flex example For the Flex version of this example, some useful tools are available. You can use data binding with E4X expressions to update the content of your controls automatically. You have access to the X M L L i s t C o l l e c t i o n class for use in your bindings. It's also possible to set the external XML document as the source for the element, instead of including all of the content within the application file. This example uses a class-based approach. Creating custom classes allows you to create reusable code that you can access in other applications. Again, you'll display the author names in a ComboBox and show their book titles in a List control. 1. Create a new Flex project with a single application file. I called the application file AuthorExample. mxml. Add an assets folder and copy the a u t h o r M o r e D e t a i l s . x m l file there. 2. Create the interface shown in Figure 4-5.
Figure 4-5. The Flex application interface
127
CHAPTER 6 The interface includes the same elements as in the Flash example. The declarative code follows. Even if you choose a different layout for the interface, use the same component id properties to ensure that the code works correctly. 3. You need to add the XML content to the application. Add an element above the interface elements with the source property set to the a s s e t s / a u t h o r M o r e D e t a i l s . x m l file, as follows:
Remember that the application doesn't reference this XML document at runtime. The content is added to the compiled SWF file, which means that you can't update the XML document and expect the application interface to update as well. You'll need to recompile the SWF application each time you change the external document. As I mentioned previously, we'll look at more dynamic approaches in the next chapter.
4. Add a class to process the content coming from the XML document. To do this, create a new class called XMLExplorer using the command File > New > ActionScript Class. Create this class file in the package XMLUtilities, as shown in Figure 4-6. Once created, the file will automatically contain the following ActionScript: package XMLUtilities { public class XMLExplorer { public function XMLExplorerQ { }
} Don't worry if the arrangement of curly braces is slightly different in your class file.
128
USING E4X EXPRESSIONS
I N e w A c t i o n S c r i p t Class
.•S3
Hew A t t k i n S c r i p L eld»» Create a new Actronscnpt dass.
Cnrlp {jpfipratinn npfinns: R Generate constructor from super d o s s ( y l Generate functions inherited from interfaces
• I
I Generate comments
® Figure 4-6. Creating the new ActionScript class 5. You need to add some private variables to the class file. Start by adding the following variables after the second line, inside the class definition line: private private private private
The XMLContent object will reference the entire XML document referred to in the authorsXML element. The c u r r X M L S t r i n g variable will provide the content for display in the TextArea. The authorXML object will contain the XMLList of all authors, and the a u t h o r s L i s t is the X M L L i s t C o l l e c t i o n associated with these authors. You'll bind to the X M L L i s t C o l l e c t i o n object, rather than to the XMLList object, because the XMLList object doesn't support binding. 6. The constructor method XMLExplorerQ must be modified. The method will receive the XML content as a parameter and will set the initial values of two of the variables you created in the previous step. public function XMLExplorer(xmlDoc:XML) { XMLContent = xmlDoc; currXMLString = XMLContent. toXMLStringQ;
} The constructor method assigns the passed-in XML object to the XMLContent object. It uses the toXMLString() method of the XML object to store the string representation in the variable currXMLString.
129
CHAPTER 4 7. The class will need two public methods: currentXML(), which returns the current XMLString variable for debugging purposes, and a l l A u t h o r s Q , which returns an XMLListCollection of all elements. Add these methods now. public function currentXML():String { r e t u r n currXMLString;
} public function allAuthorsQ¡XMLListCollection { authorXML = XMLContent..author; a u t h o r L i s t = new XMLListCollection(authorXML); currXMLString = authorXML. toXMLStringQ; return authorList;
}
The currentXML() method is self-explanatory. It returns the value of the currXMLString variable for debugging purposes. The a l l A u t h o r s Q method starts by locating the descendants of the XMLContent object using the E4X expression XMLContent. .author. The method assigns the returned XMLList to the object authorXML. The next line uses this XMLList to create the a u t h o r L i s t XMLListCollection object. The method also sets the currXMLString variable to the string representation of all elements. Adding the reference to the XMLListCollection object should add an import statement for that class, but if it doesn't, you may need to add the following line yourself: import
mx.collections.XMLListCollection;
The complete class file follows: package X M L U t i l i t i e s { import m x . c o l l e c t i o n s . X M L L i s t C o l l e c t i o n ; p u b l i c class XMLExplorer { p r i v a t e var XMLContent:XML; p r i v a t e var currXMLString:String; p r i v a t e var authorXML¡XMLList; p r i v a t e var a u t h o r L i s t : X M L L i s t C o l l e c t i o n ; p u b l i c f u n c t i o n XMLExplorer(xmlDoc:XML) { XMLContent = xmlDoc; currXMLString = XMLContent. toXMLStringQ;
} public function currentXML():String { r e t u r n currXMLString;
}
p u b l i c f u n c t i o n a l l A u t h o r s Q ¡XMLListCollection { authorXML = XMLContent..author; a u t h o r L i s t = new XMLListCollection(authorXML); currXMLString = authorXML.toXMLStringQ; return authorList;
} 130
}
}
USING E4X EXPRESSIONS 8. Switch back to the MXML application and add an block underneath the opening element. Add the following code: This code block starts by importing the FlexEvent class, which you'll need because the code calls the i n i t A p p Q function in the creationComplete event of the application. The code also imports the custom class you just created, X M L U t i l i t i e s . X M L E x p l o r e r . The code declares a bindable variable called myXMLExplorer, which is of the type XMLExplorer. It also creates the i n i t A p p Q function. This function creates the new XMLExplorer object, passing the authorsXML object as an argument. 9. You need to call the i n i t A p p Q function when the application finishes creating the interface. Add the following attribute, shown in bold, to the opening element: When the application finishes creating the interface, it calls the i n i t A p p Q function, passing a FlexEvent. You won't use this FlexEvent in the i n i t A p p Q function, but it is good practice to recognize that the object is passed with the function call. 10. Bind the ComboBox control to the X M L L i s t C o l l e c t i o n created by the class file. You can access the object with a call to the public method a l l A u t h o r s Q . The code handles this by setting the d a t a P r o v i d e r attribute for the ComboBox to the bound expression {myXMLExplorer. a l l A u t h o r s Q } . The data provider will provide access to all child elements of each element. You also need to specify a label function so you can determine what to display as the label for the ComboBox. Call this function getFullNameQ, as in the previous example. Modify the element as shown here in bold: 11. Before testing this application, you need to add the getFullNameQ function to the block. The function follows: private function getFullName(item:Object):String{ return item.authorFirstName + " " + item.authorLastName;
}
131
CHAPTER 4 This function receives an item object as an argument. This object represents the data item from the d a t a P r o v i d e r behind the current row in the ComboBox. This data item is one of the X M L L i s t C o l l e c t i o n entries, and it contains the and elements. The function returns a S t r i n g made up the element, a space, and the element. 12. You'll bind the TextArea to the value of the currXMLString in the class file by calling the currentXML() method. I've included this in the example so you can see what's going on with the XML content f r o m the authorsXML object. Modify the element as shown here in bold: 13. Test the application. Figure 4-7 shows how the interface should appear. The ComboBox is populated with author names, and the TextArea shows the complete XML content f r o m the element. Author explorer Author name
Choota author
Alison Arrb-osi O o u g l a o < < -i:l::*> L'_ c i n d - Lire o r - b e
A n 111 i n n n t r r
Figure 4-7. The interface showing the populated ComboBox component 14. At the moment, nothing happens when you choose an author from the ComboBox. The application needs to respond when the selected item changes in the ComboBox control. It must find the books associated with the selected author. To achieve this outcome, you'll bind the data provider for the List control to an E4X expression that finds the element from the ComboBox data provider. Remember that the ComboBox provides access to all child elements, including the element. Modify the List control as shown here in bold: The E4X expression a u t h o r _ c b o . s e l e c t e d l t e m . .book.bookName returns an XMLList of all elements. The expression author_cbo. s e l e c t e d l t e m references the XMLList item associated with the chosen author . From there, the E4X expression finds the descendant and returns the element. You'll be able to follow this path more easily once you display the XML content in the TextArea control.
USING E4X EXPRESSIONS
15. The final step is to display the XML details of the author in the TextArea, Modify the as shown here in bold: New > ActionScript Class. This class file will handle the loading and parsing of the external XML content. Figure 5-3 shows the settings for this class. I Mew ActionScript Class
OS
Hew A c t i o n S c r i p t c l a s s Create a new ActionScript dass.
} 3. Modify the class file to make it bindable. This makes all public methods available for use in binding expressions. Add the following line above the class declaration: [Bindable]
147
4. Add the following import statements below the first line package declaration. These statements reference the class files that you'll need to use. import flash.net.URLLoader; import flash.net.URLRequest; import mx.collections.XMLListCollection; 5. Add the following private variable declarations beneath the class file declaration: private private private private
The first variable, xmlAHContent, refers to the content loaded from the external XML document. The code uses the name xmlLoader for the URLLoader object. The childElements object refers to the XMLList of child elements that will be returned from the loaded content. This content will ultimately populate the data provider of the ComboBox. However, it will do so via an intermediate class. The application will use the X M L L i s t C o l l e c t i o n object a l l E l e m e n t s C o l l e c t i o n as a wrapper class when assigning the data provider. 6. Modify the constructor method to create the URLLoader object and add an event listener to handle the complete event. The new lines appear in bold in the following code block: public function MyXMLLoaderHelperQ { xmlLoader = new URLLoader()j xmlLoader.addEventListener(Event.COMPLETE,
completeHandler)j
} 7. The code will call the l o a d ( ) method of the URLLoader object in a public method called loadXML(). The application can then control when to call this method. The loadXML() method will receive the name of the external document to load as an argument. public function loadXML(xmlFile:String):void { try { xmlLoader.load(new URLRequest(xmlFile));
} catch (e:Error) { trace ("Can't load external XML document");
}
You'll notice that the function includes a t r y / c a t c h statement to provide some very basic error handling. In the case of an error, debugging the application will display a message in the Console view. This could be a little more robust, but the code will suffice for this simple example. 8. When the external document finishes loading, the completeHandlerQ method will be called. You specified this handler in the constructor method, but didn't add the private method to the class file. Add this method now, as shown in the following code block: private function completeHandler(e:Event):void { xmlAHContent = XML(e.target.data); trace (xmlAllContent.toXMLStringQ); dispatchEvent(new Event(Event.COMPLETE));
}
USING THE URLLOADER CLASS WITH XML DOCUMENTS This method is private because it doesn't need to be accessed by the SWF application. The method includes a debugging line to display the loaded content in the Console view. Tracing loaded data is a useful tool, as it helps to check that you're loading the content that you expect. The completeHandler() method also dispatches a complete event to the SWF application. The application can listen for this event and respond appropriately. 9. The custom class also needs to provide a method to locate the elements that will populate the data provider for the ComboBox. This method, g e t C h i l d E l e m e n t s Q , will receive the name of the specific child element to locate in the loaded content as an argument. In this case, the elements are a direct child of the root element in the XML document, so you can use the c h i l d Q XML class method. Add the following method to the class file: public function getChildElements(elementName:String): «-XMLListCollection { childElements = xmlAHContent. child (elementName); trace (childElements. toXMLStringQ); childElementsCollection = new XMLListCollection(childElements); return childElementsCollection;
The g e t C h i l d E l e m e n t s Q method finds all child elements with the specified name using the c h i l d ( ) method of the XML class. You need to use this approach, rather than an E4X expression, because g e t C h i l d E l e m e n t s Q receives a S t r i n g value for the child element name. This method returns an XMLList of the matching elements. The code block includes a t r a c e ( ) action to display the returned elements so you can check that the XMLList contains the correct information. Again, this action is included only for debugging purposes, and you would probably comment it out when moving the application to production. The method finishes by assigning the XMLList to the c h i l d E l e m e n t s C o l l e c t i o n object. The method then returns this X M L L i s t C o l l e c t i o n object. You can call this method to provide a data source for any data-aware component. In our case, that will be the ComboBox control. The complete class file follows so you can check to make sure your code is correct so far. package xmlUtilities { import flash.net.URLLoader; import flash.net.URLRequest; import mx.collections.XMLListCollection; [Bindable] public class MyXMLLoaderHelper { private var xmlAHContent:XML; private var xmlLoader:URLLoader; private var childElements ¡XMLList; private var childElementsCollection: XMLListCollection; public function MyXMLLoaderHelperQ { xmlLoader = new URLLoaderQ; xmlLoader.addEventListener(Event.COMPLETE,
completeHandler);
} 149
CHAPTER 6 public function loadXML(xmlFile:String):void { try { xmlLoader.load(new URLRequest(xmlFile));
} catch (e:Error) { trace ("Can't load external XML document");
}
}
public function getChildElements(elementName:String): «-XMLListCollection { childElements = xmlAllContent.child(elementName); trace(childElements.toXMLString()); childElementsCollection = new XMLListCollection(childElements); return childElementsCollection;
10. Switch back to the application file for your project. You'll create an interface similar to that used in the Flash example. The interface contains Label controls and a ComboBox component. Create the interface for the Flex application using the following code: Loading external XML Author name
Choose author
Figure 5-4. The Flex interface
150
Figure 5-4 shows how the interface should appear. The ComboBox has the ID author cbo.
USING THE URLLOADER CLASS WITH XML DOCUMENTS 11. Add a creationComplete attribute to the element, as shown here in bold: When the creation of the interface completes, the application will call the i n i t A p p ( ) function. 12. Add the following script block to the application:
The code starts by importing the classes needed for the application. This includes FlexEvent, passed by the i n i t A p p ( ) function; the custom class MyXMLLoaderHelper; and the X M L L i s t C o l l e c t i o n class, required for the ComboBox data provider. The code block declares a private variable for the MyXMLLoaderHelper object, called myXMLLoader. It also creates a variable called authorsXMLLC, which will be used as the data provider for the ComboBox control. The element includes the i n i t A p p ( ) function, called when the interface has finished creating. This function creates a new MyXMLLoaderHelper object, assigns a complete event listener, and calls the loadXMLQ method of this object, passing the name of the file to load. You'll also see an empty completeHandler() function, which will be populated a little later. 13. Now it's time to do some testing. Debug the application to check that the class file is able to load the XML document correctly. You should see the loaded XML content in the Console view, as shown in Figure 5-5.
(L P r o b l e m s
@ Console
«
authorsAndBooks
[• [Flex
Application]
;SWF; E : \ a i p \ c l i e n t s \ A p r e s s - F r i e n d s O f E d \ X M L a n d E 4 X f o r Friendly 3ooks lishYear>2008lishYear> Alison Arfitrose
l You can find the files authorsAndBooks.mxml and MyXMLLoaderHelper.as saved with the other chapter resources. These examples didn't pass variables to the requested page. That's the topic of the next section.
153
CHAPTER 6
Updating content with the URLLoader class As you saw earlier, you can pass variables to the requested page with the URLLoader object. You might use this approach to send a parameter to a server-side file so that you can filter the content returned to the object. You could also use the approach to send values for updating by the server-side page. Whatever your reason, you will need to work with a URLVariables object. Earlier in the chapter, we covered how to create a URLVariables object and set variables and values. The following code provides a quick refresher: var params:URLVariables = new URLVariablesQ; params.continent = "Europe"; params.timeStamp = new Date().getTimeQ; Once you've created the URLVariables object, you need to set it as the data property of the URLRequest object that you'll pass to the URLLoader. You can see how to do this in the following lines: var request:URLRequest = new URLRequest(strURL); request.data = params; The next example demonstrates how to send values from a SWF file, using the URLVariables object. We'll work through both Flash and Flex versions that demonstrate sending login details with the request. This practice is common when users needs to log in to an application before they can access any details. The interface in both cases will consist of text fields that allow a user to enter a username and password, as well as a button to submit the details. The application will also display a message f r o m the requested file. In this case, you will work with a static XML document, so it won't matter which values you pass. In Chapter 9, you'll see how to integrate this approach with server-side pages to retrieve a response. For now, these simple examples will get you started. We'll begin with the Flash example. Like the previous Flash examples, it uses procedural code to keep the example simple. You can see a class-based solution in the Flex example that follows the Flash example.
Sending variables in a Flash application Here are the instructions to set up the Flash example: 1. Create a new ActionScript 3.0 document and add the interface elements shown in Figure 5-7. Login Username
Password |
Log in
|
Figure 5-7. The Flash login example interface
154
The interface consists of some static text, two Textlnput controls, a Button component, and a dynamic text field. The Textlnput controls have the instance names username_ txt and password_txt, respectively. The Button is called login_btn and has the label Log in. The dynamic text field has the instance name message_txt. In the Parameters tab, set the displayAsPassword field to true for the password_ txt instance.
USING THE URLLOADER CLASS WITH XML DOCUMENTS 2. Add a new layer called actions and open it in the Actions panel with the F9 shortcut key. Add the following ActionScript code to set up the application: var sender:URLLoader; var sendPage:URLRequest; var sendVars:URLVariables; initAppQ; The first three lines declare the variables that the application needs. First, the sender variable is a URLLoader object that will send the request. The second variable, sendPage, is a URLRequest object that details the page that the application will request. sendVars, the last variable, is a URLVariables object that will pass the username and password. The final line calls the i n i t A p p ( ) function, which you'll create in the next step. 3. The i n i t A p p ( ) function will need to create the URLLoader, URLRequest, and URLVariables objects. The URLRequest object will need to specify the page to be requested. Add the following function underneath the function call f r o m step 2: function initApp():void { sender = new URLLoaderQ; sendPage = new URLRequest("message.xml"); sendPage.method = URLRequestMethod.POST; sendVars = new URLVariablesQ;
} In this example, instead of working with a server-side page, the application calls a simple XML document. Because it sends values to a static XML document, you need to use the POST method; otherwise, the application will throw an error. There won't actually be any processing of the username or password values. 4. The application will send the variable names and values to the XML document in response to the l o g i n _ b t n button click. You'll need to add an event listener to the Button that listens for the c l i c k event. I've called this function c l i c k H a n d l e r Q . Modify the i n i t A p p Q function as show here, to include the line shown in bold: function initApp():void { sender = new URLLoaderQ; sendPage = new URLRequest("message.xml"); sendPage.method = URLRequestMethod.POST; sendVars = new URLVariablesQ; login_btn.addEventListener(MouseEvent.CLICK,
clickHandler)j
} 5. Add the c l i c k H a n d l e r Q function that follows. At this stage, the code includes a simple t r a c e ( ) statement so you can test your code, but you'll add something more useful shortly. function clickHandler(e:MouseEvent):void { trace ("I'm clicked");
} 6. Test the application with the Ctrl/Cmd+Enter shortcut. You should be able to click the button and see the message I'm clicked in the Output panel.
155
CHAPTER 5 7. You now need to modify the c l i c k H a n d l e r ( ) function so it actually performs a task. First, it will check that values have been entered for both the username and password. If not, an error message will display. If the user enters both values, the function will add them to the sendVars URLVariables object and request the external document with the URLLoader object. It will also need to clear any existing value from the messagejtxt text field. Add the following function to the actions layer: f u n c t i o n clickHandler(e:MouseEvent):void { if ( u s e r n a m e j t x t . t e x t . l e n g t h && p a s s w o r d j t x t . t e x t . l e n g t h > 0) { messagejtxt.text = " " ; sendVars.username = usernamejtxt.text; sendVars.password = p a s s w o r d j t x t . t e x t ; sendPage.data = sendVars; sender.load(sendPage);
} else { messagejtxt.text = "You must enter both a username and password « b e f o r e c l i c k i n g the b u t t o n . "
}
8. You don't yet have a function to respond to the requested page, so you need to add an event listener that responds to the complete event of the URLLoader. Add the following line to the i n i t A p p ( ) function: sender.addEvent Listener(Event.COMPLETE,
completeHandler);
9. Now you need the function that responds when this event is dispatched. Add the following completeHandler() function: f u n c t i o n completeHandler(e:Event):void { messagejtxt.text = X M L ( e . t a r g e t . d a t a ) , t o S t r i n g ( ) ;
} The completeHandler() function finds the loaded content using e . t a r g e t . d a t a , and casts it as an XML object using the XML() constructor method. The function displays a string representation of the returned content in the dynamic text field using the t o S t r i n g ( ) method. The external XML document contains a single element: Login successful . Because this is a simple element, without children, the t o S t r i n g ( ) method returns only the text inside the element. Login Username
53 5
Password ***" Lcc; in
Login successful
Figure 5-8. Testing the Flash application
156
3
10. You can't test this example until you copy the message.xml document from the chapter resources to the same folder as your Flash application. Once you've done so, test the file and click the Log in button. You should see the message Login successful displayed in the dynamic text field, as shown in Figure 5-8.
USING THE URLLOADER CLASS WITH XML DOCUMENTS
In this example, you can't tell whether the variables have been sent successfully to the XML document, because no processing occurs in that page. You'll see some more sophisticated examples of sending variables with a URL Loader object in Chapter 9, where the variables that you send will elicit a response from the requested page.
The complete code from the actions layer follows. Check it against your own code to make sure that you don't have any errors. var sender:URLLoader; var sendPage:URLRequest; var sendVars:URLVariables; initAppQ; function initApp():void { sender = new URLLoaderQ; sendPage = new URLRequest("message.xml"); sendPage.method = URLRequestMethod.POST; sendVars = new URLVariablesQ; login_btn.addEventListener(MouseEvent.CLICK, c l i c k H a n d l e r ) ; sender.addEvent Listener(Event.COMPLETE, completeHandler);
} f u n c t i o n clickHandler(e:MouseEvent):void { if ( u s e r n a m e _ t x t . t e x t . l e n g t h && p a s s w o r d _ t x t . t e x t . l e n g t h > 0) { message_txt.text = " " ; sendVars.username = username_txt.text; sendVars.password = p a s s w o r d _ t x t . t e x t ; sendPage.data = sendVars; sender.load(sendPage);
}
else { message_txt.text = "You must enter both a username and password ^»before c l i c k i n g the b u t t o n . "
}
}
f u n c t i o n completeHandler(e:Event):void { message_txt.text = X M L ( e . t a r g e t . d a t a ) , t o S t r i n g ( ) ;
}
You can find the files for this example saved as loginExample.fla and message.xml with the other chapter resources. If you want to see another event in action, try removing the sendPage.method property and add an event handler listening for an IOError. Removing the method property will make the example use the GET method. This method will cause an IOError when you try to request the static XML document. You can see a sample of this error-handling function in the resource file.
157
CHAPTER 6
Sending variables in a Flex application Let's re-create the previous example using Flex Builder. In this case, the example uses a custom class file to work with the content. 1. Create a new Flex project and application file with the name of your choosing. Add an assets folder and copy the message.xml file there. 2. Use the File > New > ActionScript Class command to create a new ActionScript class. Add the class to the xmlUtilities package and call it XMLLoaderWithVariables. 3. Add the following import statements beneath the package declaration. These statements reference the classes that the application needs to use. Note that these statements will also be added automatically when you declare the variables in step 4. import import import import
You could replace these import statements with one that imports all classes in the flash.net package at the same time: import flash.net.*. Even though that statement is valid and much shorter than my approach, my preference is still to import classes individually so that I can see which I'm using in the current application. Feel free to use the wildcard instead if you prefer.
4. Add a bindable declaration above the class declaration. This declaration makes all public methods and properties bindable in the application file. [Bindable] 5. Declare the following private variables below the class declaration: private var xmlMessage:XML; private var xmlLoader¡URLLoader; private var xmlRequest:URLRequest; The first variable, xmlMessage, will store the returned XML content f r o m the document. The second variable, xmlLoader, refers to the URLLoader object that will make the request. The third variable, xmlRequest, refers to the URLRequest object that will handle the page request and passed variables. 6. Modify the constructor method as shown here: public function XMLLoaderWithVariablesQ { xmlLoader = new URLLoaderQ; xmlLoader.addEvent Listener(Event.COMPLETE, completeHandler);
} The XMLLoaderWithVariablesQ method creates the URLLoader object and adds an event listener that responds when the operation is complete. When the external file finishes loading, the handler completeHandler will execute.
158
USING THE URLLOADER CLASS WITH XML DOCUMENTS 7. Add the completeHandlerQ method now as shown here: private function completeHandler(e:Event):void { xmlMessage = XML(e.target.data); dispatchEvent(new Event(Event.COMPLETE));
} This method assigns the loaded XML content from the external file to the object xmlMessage, casting it to the type XML with the XML() constructor method. The method then dispatches the complete event to the application so it will know that the request has finished and the loaded data is available. 8. You'll need a public method that handles the request for the external file. This method, loadXMLQ, will receive two arguments: the name of the file to request and the variables to send with that request. When the Flex application calls the loadXMLQ method, it will pass a URLVariables object as the second argument. public function loadXML(xmlFile:String, vars:URLVariables):void { try { xmlRequest = new URLRequest(xmlFile); xmlRequest.data = vars; xmlRequest.method = URLRequestMethod.POST; xmlLoader.load(xmlRequest);
} catch (e:Error) { trace ("Can't load external XML document");
}
This method encloses the content in a t r y / c a t c h block to provide some simple error handling. In the case of an error, the application will display the message Can't load external XML document in the Console view. You would probably make the error handling a little more robust in a real-world application, so feel free to modify the code at this point if you want. The loadXMLQ method creates a new URLRequest object, using the URL passed in as the first argument. The method assigns the variables argument to the data property of the URLRequest object and sets the method property to POST the variables to the requested page. It finishes by calling the l o a d Q method to make the request. 9. The XMLLoaderWithVariables class will need one public method to return the content f r o m the external document. The external document will send back a single simple element, . Applying the t o S t r i n g Q method will result in only the text from the element being identified. Add the following public messageQ method to the class file: public function messageQ:String { return xmlMessage.toStringQ;
}
159
CHAPTER 6 That's it for the class file. Check your contents against the complete code block that follows: package xmllltilities { import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLRequestMethod; [Bindable] public class XMLLoaderWithVariables { private var xmlMessage:XML; private var xmlLoader¡URLLoader; private var xmlRequest:URLRequest; public function XMLLoaderWithVariablesQ { xmlLoader = new URLLoaderQ; xmlLoader.addEvent Listener(Event.COMPLETE, completeHandler);
} public function loadXML(xmlFile:String, vars:URLVariables):void { try { xmlRequest = new URLRequest(xmlFile); xmlRequest.data = vars; xmlRequest.method = URLRequestMethod.POST; xmlLoader.load(xmlRequest);
}
catch (e:Error) { trace ("Can't load external XML document");
}
}
public function message()¡String { return xmlMessage.toString();
}
private function completeHandler(e:Event):void { xmlMessage = XML(e.target.data); dispatchEvent(new Event(Event.COMPLETE));
}
10. Switch back to the Flex application file. Create the interface shown in Figure 5-9. Login U Bern a m « password
The interface consists of some Label controls, as well as two Textlnput components for collecting the username and password. The second component has its displayAsPasword setting set to true. There is also a Log in button, which will trigger the URLLoader request, as well as a TextArea, which displays messages. If you change this interface, make sure that you use the same ID settings for all components.
Figure 5-9. The Flex login application interface
160
USING THE URLLOADER CLASS WITH XML DOCUMENTS The declarative code to create the interface follows: 11. You'll set up the application by calling a function in the creationComplete attribute of the element. This function is called i n i t A p p Q , and it will set up the variables you'll need, including a new XMLLoaderWithVariables object. Modify the element as shown here in bold: 12. Add the following , including the i n i t A p p Q function. I'll explain it after the code block.
161
CHAPTER 5 This element starts by importing the relevant classes. The application needs the FlexEvent class, as this type of event is dispatched when the interface finishes creating. It also needs to import the custom class that you just created, XMLLoaderWithVariables, and to create an object of this type called myXMLLoaderVars. The i n i t A p p ( ) function receives a FlexEvent as an argument. It starts by creating a new instance of the XMLLoaderWithVariables class and then assigns the completeHandler() function to be called when the URLLoader finishes loading the external document. At the moment, this function doesn't contain any code. The function also assigns an event listener to the l o g i n _ b t n instance. This listener responds to the c l i c k event of the button with the c l i c k H a n d l e r Q function. The block also contains an empty c l i c k H a n d l e r Q function. 13. When the Log in button is clicked, the application needs to check that the username and password are filled in before calling the loadXML() method of the XMLLoaderWithVariables object. When the loadXMLQ method is called, the method call will pass the URL of the document to load, as well as a URLVariables object containing the entered username and password. Modify the c l i c k H a n d l e r Q function as shown here in bold: p r i v a t e f u n c t i o n clickHandler(e:MouseEvent):void { if (username_txt.text.length > 0 && password_txt.text.length > 0) { var myVars¡URLVariables = new URLVariablesQj myVars.username = username_txt.textj myVars.password = password_txt.textj myXMLLoaderVars.loadXML("assets/message.xml", myVars)j
} else { message_txt.text = "You must enter both a username and password ^»before clicking the button";
}
}
The c l i c k H a n d l e r Q function starts by testing that the length of both the username and password entries is greater than 0. If this is the case, the function creates a new URLVariables object and assigns the username and password properties from the entries in the Textlnput controls. It then calls the loadXMLQ method, passing the name of the document to request and the URLVariables object. If either of the Textlnput controls do not contain any text, the message You must enter both a username and password before clicking the button displays in the TextArea at the bottom of the interface. 14. The last step in building this application is to respond when a reply is received from the messages.xml file. The application will display the reply in the TextArea control. It does this in the completeHandlerQ function, so modify it as shown in bold here: p r i v a t e f u n c t i o n completeHandler(e:Event):void { message_txt.text = myXMLLoaderVars.messageQj
}
162
USING THE URLLOADER CLASS WITH XML DOCUMENTS 15. Now you're ready to test the application. Run it and enter values for a username and password. When you click the button, you should see the TextArea update, as shown in Figure 5-10. Login Username
;.
password
i
L
'
g
'
n
_4
Login success Fui
Figure 5-10. The completed Flex login application example The complete code for the application file follows: 0 && password_txt.text.length * > 0) { var myVars:URLVariables = new URLVariablesQ; myVars.username = username_txt.text; myVars.password = password_txt.text; myXMLLoaderVars.loadXML("assets/message.xml", myVars);
}
else { message_txt.text = "You must enter both a username and «•password before clicking the button";
} ]]>
}
163
CHAPTER 6 You can find the resource files for this example with the other chapter resources, saved as LoginExample.mxml and XMLLoaderWithVariables.as. You've seen several examples of loading external content using the URLLoader class. When you request external data, you're subject to Flash Player security restrictions, so it's important to understand these and the limitations that apply.
Understanding Flash Player security When accessing external documents, the security model in Flash Player 9 and 10 is based on the relative locations of the SWF file loading the data and the source of the data. The rules discussed in this section also apply to Flash Player 8, although Flash Players 9 and 10 have some extra restrictions discussed in the section entitled "Finding problems with the cross-domain policy file." Note that earlier versions of Flash Player have different approaches to security. I'll refer only to Flash Player 10 here, as it is the most recent player at the time of writing, but the same rules apply to Flash Player 9, too. The basic rule is that a SWF file can access any data from the same subdomain as its own location. By default, it's not possible for the SWF file to load content from a different domain or subdomain. This restriction also applies between local and network domains. SWF files on the network cannot access local data, and local SWF files cannot access network data, because they're in different domains.
Understanding security sandboxes Flash Player 10 allocates SWF files to their own security sandbox, which equates to their exact domain. For example, SWF files located in the following domains are considered to be in separate sandboxes: •
http://www.-Friendsofed.com
•
http://friendsofed.com
• http://examples.friendsofed. com •
164
http://65.19.150.101
USING THE URLLOADER CLASS WITH XML DOCUMENTS Even though the IP address 65.19.150.101 may resolve to the first domain in this list, it is still considered to be in a separate security sandbox. If you need to load data f r o m another domain into your Flex application, you can choose f r o m the following two alternatives: •
Specifically allow access by using a cross-domain policy file on the server hosting the data source
•
Use a server-side proxy file to access the remote data and locate it within the local domain
Creating a cross-domain policy file A cross-domain policy file is an XML file called crossdomain.xml, which lives in the root of the web server that hosts the external data. The file does not live in the same domain as the requesting SWF application. The cross-domain policy file grants permissions to specific domains to access the data stored there. If the crossdomain.xml file does not reside in the root directory of the server, the SWF file can request it f r o m a different location using the S e c u r i t y . l o a d P o l i c y F i l e method. The cross-domain policy file will apply only to the directory f r o m which it is loaded and any child directories. For example, you might use this method to restrict access to content in the _data folder and any child folders.
Writing a cross-domain policy file The cross-domain policy file needs to have the following structure. For Flash Player 10, this structure must be exact. This cross-domain policy file allows access to the data by www.friendsofed.com, any subdomain of friendsofed.com, and the IP address 65.19-150.101. Note that you can use the wildcard * to specify any subdomain. You can also use a wildcard to grant access to all domains: If the SWF requesting the data appears in any of the domains listed in the cross-domain policy file, it will be granted access to the content by Flash Player 10. If not, the SWF won't be granted permission to load the data, and the application will generate a s e c u r i t y E r r o r .
165
CHAPTER 6 If you're working with secure domains, it's possible to include the secure attribute within an tag. This attribute has a default value of t r u e , which restricts data on a secure HTTPS server from being accessed by anything other than another HTTPS server. You can set this value to f a l s e if you want a secure server to be able to be accessed by both secure and insecure servers.
Issues with the cross-domain policy file Be aware that Flash Player 10 has tightened up some of the requirements for cross-domain policy files. First, Flash Player 10 will recognize cross-domain policy files only where the content type is set to any text type ( t e x t / * ) , or to a p p l i c a t i o n / x m l or application/xhtml+xml. The content type is set by the response headers provided by the HTTP server, so you may need to check that the server settings are correct if you're having difficulties with the policy file. In addition, Flash Player 10 will reject any policy file with contents that are not well-formed or that are invalidly formatted. The root element of the file must be , and none of the elements in the file can contain text children. In addition, if the cross-domain policy file contains characters before or after the opening and closing tag, with the exception of legal declarations, Flash Player 10 will reject the file. If the HTTP server redirects a cross-domain policy file to a location within the same domain, Flash Player 10 will treat the redirected location as the final destination, not the initially requested URL. Earlier versions of Flash Player did the opposite, treating the initial location as the final destination and ignoring the location of the redirect. Because the final destination dictates which domain and subdomain will be accessible to Flash Player 10, any HTTP server redirects could potentially cause problems. If the policy file doesn't reference the redirected location, Flash Player 10 will not access the external data. If you don't have access to a cross-domain policy file on the remote server, you can proxy the external data locally.
Proxying data locally If you can't add a cross-domain policy file to a remote server, you can use a server-side file to request the external content and provide it to your SWF application. This is likely to be particularly useful where you're requesting an XML-formatted stream from an external source, perhaps as a web service. As long as the server-side file is in the same domain as the SWF application, you'll be able to load the proxied content. The application will think that the data is local and therefore in the same security sandbox. You can write the simple server-side proxy file in the language of your choice. You can find out more about the topic at h t t p : / / k b . a d o b e . c o m / s e l f s e r v i c e / v i e w C o n t e n t . d o ? e x t e r n a l I d = t n _ l 6 5 2 0 & s l i c e l d = l . At the time of writing, the article had examples of proxy files written in ColdFusion, PHP, ASP, and using a Java Servlet. LiveCycle Data Services ES also provides a complete proxy management system for Flex applications.
166
USING THE URLLOADER CLASS WITH XML DOCUMENTS
Summary This chapter covered the URLLoader class and discussed its properties, methods, and events. We worked through Flash and Flex examples to demonstrate how to use this class and related classes to request external content. We finished by looking at some of the security issues surrounding Flash Player. The next chapter covers some of the loading methods that are specific to Flex.
167
Chapter 6
LOADING METHODS SPECIFIC TO FLEX In the previous chapter, I showed you how to load external XML content into both Flash and Flex using the URLLoader class. You saw that you can load content from a static XML file or from a server-side page that generates an XML stream. Both Flash and Flex can use the URLLoader class. The advantage of loading external content is that your applications will be more flexible than if you store the content within the application itself. While Flex can use the URLLoader class, it can also use the HTTPService class to access many types of external content, including XML-formatted content. It's possible to either use a tag-based approach with the MXML element or to write ActionScript 3.0 code to work with the HTTPService class. In this chapter, I'll cover both options and show you how to use them to load external XML files. As with the URLLoader class, when you use the HTTPService class or element to load external XML content, you can access the loaded XML tree using methods of the XML class or by writing E4X expressions. Unlike the URLLoader class, in which all content arrives as text, with the HTTPService class and element, you can specify the format for the loaded content in advance, so there's no need to cast the loaded content as an XML data type. The HTTPService class and element both use a request-response approach. This means that any application using them needs to request the content from the server first, before the XML content is provided to the Flex application. If the external data changes, the server cannot initiate the process and inform the Flex application that something has changed. So, if you are working with external content that changes regularly, you'll need to poll the server at regular intervals in order to detect if the content has changed. This process is likely to be unwieldy, so
169
CHAPTER 6 you may wish to look at an approach involving the XMLSocket class. You can use the XMLSocket class to create a real-time connection to XML content. When the content changes, the server can notify the Flex application of any changes, causing the application to update without first making a request. The XMLSocket class requires that the application runs on a socket server. That topic is beyond the scope of this book. In this chapter, we'll focus on the element and HTTPService class. I'll explain the properties, methods, and events of both, and show you how they differ. The code samples will illustrate how to use MXML tags and also demonstrate a class-based approach. Remember that the loading of external content is subject to the security restrictions of Flash Player. Each version of the Flash Player has slightly different security considerations. At the time of writing, the latest version was Flash Player 10. You can find out information about Flash Player 10 security in Chapter 5 of this book. As usual, you can download all of the resources for this chapter from http://www.-Friendsofed.com. This download includes the XML documents used for the examples, as well as my completed files. Let's begin!
Loading external content Both the tag and HTTPService class allow you to request content from a URL and to receive a response. You can optionally send parameters with the request, perhaps if you need to filter the content. The tag exists in the mx.rpc.http.mxml package; the HTTPService class is in the m x . r p c . h t t p package. Both work in much the same way, although there are some subtle differences between the two. As with the URLLoader class, when using either the tag or HTTPService class, processing of the loaded content should wait until the request has successfully completed. The application is notified either when it receives the results of the request or a fault is generated. I'll deal with the tag and HTTPService class separately. We'll start by looking at the tag.
Using the tag The MXML element provides a tag-based approach that developers can use to request an external document and access its content. The element works with only the Flex framework and has no equivalent in Flash. The element is useful for developers who don't wish to adopt a scripted approach in their applications. Thanks to data binding, it's possible to request a document, access the contents, and display them in a Flex interface without writing a single line of ActionScript 3.0. Before we see this element in action, let's start by looking at the properties, methods, and events of the tag.
170
LOADING METHODS SPECIFIC TO FLEX
Properties of the tag The properties of the HTTPService tag are shown in Table 6-1. Table 6-1. Properties of the tag
Property
Data type
Description
Default value
channelSet
ChannelSet
Provides access to the ChannelSet used by the service. These are the channels used to send messages to the destination.
concurrency
String
Indicates how to handle multiple calls to the same service. The choices are m u l t i p l e , s i n g l e , and l a s t .
multiple
contentType
String
Specifies the type of content for the request. The choices are a p p l i c a t i o n / x-www-form-urlencoded and application/xml.
application/ x-www-formurlencoded
destination
String
Indicates the HTTPService destination name specified in the s e r v i c e s - c o n f i g . xml file. This file is used with LiveCycle Data Services ES.
headers
Object
Custom headers to send with the HTTPService.
lastResult
Object
The result of the last request made to the service.
makeObjectsBindable
Boolean
Determines whether returned anonymous objects are forced to bindable objects.
method
String
The HTTP method used to send the request. Choose f r o m GET, POST, HEAD, OPTIONS, PUT, TRACE, and DELETE. Chapter 9 explains more about the GET and POST methods.
request
Object
An object containing name/value pairs that are parameters for the requested URL.
requestTimeout
int
Sets the timeout for the request in seconds.
GET
Continued
171
CHAPTER 6 Table 6-1. Continued
Property
Data type
Description
Default value
resultFormat
String
Indicates the expected format for returned content f r o m the request. Choose from o b j e c t , a r r a y , xml, •flashvars, t e x t , and e4x.
object
rootURL
String
The URL to use as the basis for calculating relative URLs. Used only when useProxy is set to t r u e .
url
String
The URL or location for the service.
showBusyCursor
Boolean
Determines whether to display a busy cursor while the request is loading.
false
useProxy
Boolean
Determines whether to use the Flex proxy service.
false
xmlDecode
Function
Sets the function to use to decode XML returned with the r e s u l t F o r m a t of o b j e c t .
xmlEncode
Function
Sets the function to use to encode a service request as XML.
Some of these properties need a little more explanation:
172
•
concurrency: The concurrency property dictates how the application should deal with multiple calls made to the same service. The default value of m u l t i p l e indicates that multiple requests to the same service are allowed. With this setting, the developer would need to manage the response from each request separately to make sure that the results don't get mixed up. A concurrency value of s i n g l e allows the application to make only a single request at a time. If the application makes more than one request, these additional requests will generate a fault. Setting the value of the concurrency property to l a s t means that when the application makes a request, any preexisting requests to that service are canceled. You might use this value if you provide the functionality for canceling requests in your application.
•
contentType: You can set the contentType property to indicate what type of content the request sends to the target URL. The default value of a p p l i c a t i o n / x - w w w - f o r m - u r l e n c o d e d sends the request as name/value pairs. You can override this default value and specify that the content sends the request in XML format by using the a p p l i c a t i o n / x m l setting.
•
headers: The headers property allows you to set custom headers that will be passed with the request. At the time of writing, there is a known bug that prevents custom headers from being sent when the HTTP GET method is selected. The bug is documented at h t t p s : / / b u g s . a d o b e . com/jira/browse/SDK-12505. The work-around is to use the POST method instead.
LOADING METHODS SPECIFIC TO FLEX • method: You can set the method property to indicate which HTTP method should be used to send the request. If you don't set a value, the request will be made using GET. As detailed in the previous paragraph, you may need to set the method to POST in order to pass custom headers. Unless you go through the server-based proxy service, you can use only HTTP GET or POST methods with the request. However, the other methods become available if you set the useProxy property to t r u e and use the server-based proxy service. This topic is beyond the scope of this book; consult the Flex help for more information. •
request: If you need to send parameters or variables with the request, the request property passes an Object of name/value pairs. However, you would need to pass an XML object instead if the contentType property is set to a p p l i c a t i o n / x m l .
•
requestTimeout: You can set a timeout for the request with the requestTimeout property. The property uses a value in seconds. You can bypass this property and consequently allow no timeout by using a value of zero or less. You might set a timeout so you can prevent the user from waiting if there is no response from the web server in a reasonable amount of time. If you don't set a timeout, the request might fail without the user being aware that there is a problem.
•
resultFormat: The resultFormat property indicates the expected format for the returned results. The default value, o b j e c t , expects a returned XML value and parses it as a tree of ActionScript objects. A resultFormat value of array expects an XML value, which is parsed as a tree of ActionScript objects. The difference from the first value, o b j e c t , is that if the top-level object is not an Array, a new Array is created, and the result is set as the first item. When loading XML content, you need to set the value of the resultFormat to e4x if you want to take advantage of the new XML class and traverse the content using E4X expressions. The e4x value returns the content as literal XML. It's confusing but you shouldn't use the resultFormat of xml when you work with ActionScript 3.0. You should use this value only if you want to work with the ActionScript 2.0 XML class. This book addresses mainly ActionScript 3.0, so we won't look at the legacy XML class at all. The other two values for the resultFormat property are f l a s h v a r s and t e x t , f l a s h v a r s returns text containing name/value pairs separated by ampersands. If you've worked with the LoadVars class in ActionScript 2.0, you'll be familiar with this format. The t e x t value returns the content as raw text.
•
u r l : The u r l property determines the URL or page used in the request.
•
showBusyCursor: The showBusyCursor property does exactly as its name suggests: it displays an hourglass busy cursor while the application makes a request. This property is available only to the MXML tag and it can't be scripted (which I think is a great pity).
I'll show you how these properties work shortly, in the "Putting it all together" section. Next, let's look at the methods of the tag.
173
CHAPTER 6
Methods of the tag Table 6-2 summarizes the main methods of the HTTPService tag. Table 6-2. Methods of the HTTPService tag
Description
Returns
cancelQ
Overrides the most recent request
AsyncToken
disconnect()
Disconnects the network connection of the service without waiting for the request to complete
Nothing
Method
Parameters
HTTPServiceQ
rootURL¡String, d e s t i n a t i o n : S t r i n g
Constructor method
Nothing
sendQ
parameters: Object
Executes the HTTPService request
AsyncToken
The d i s c o n n e c t ( ) method is straightforward. The other methods work as follows: •
c a n c e l Q : The c a n c e l Q method cancels the most recent HTTPService request. It returns an AsyncToken object, which provides a way to set token-level data for remote procedure calls so you can identify each one individually.
•
HTTPServiceQ: The constructor method HTTPServiceQ instantiates an HTTPService object. It can take an optional rootURL value as an argument, which specifies the root to use when calculating relative URLs. The method can also take an optional d e s t i n a t i o n argument, which corresponds to an HTTPService destination name in the s e r v i c e s - c o n f i g . x m l file. That topic is a little beyond the scope of this book, so I won't go into it here, other than to say you might use this approach if you're working with Flex Data Services or making a non-HTTP request.
•
send(): The send() method is probably the most important method of all. It actually sends the request, optionally with an Object containing name/value parameters for the request. If the contentType property is set to a p p l i c a t i o n / x m l , the application would need to pass an XML object with the request.
Let's move on to events next.
Events of the tag The HTTPService tag dispatches a number of events, as summarized in Table 6-3. Unlike the progress and h t t p S t a t u s events dispatched by the URLLoader, there aren't any events here to determine the progress of the request.
174
LOADING METHODS SPECIFIC TO FLEX Table 6-3. The events dispatched by the HTTPService class
Event
Type
Description
fault
FaultEvent
Dispatched when an HTTPService call fails
invoke
InvokeEvent
Dispatched when the HTTPService call is invoked, providing an error isn't encountered first
result
ResultEvent
Dispatched when an HTTPService call returns successfully
Of these, you're most likely to use the f a u l t and r e s u l t events.
Putting it all together Let's see how these properties, methods, and events work together to create an HTTPService request using a tag-based approach. A little later, you'll see how to accomplish the same tasks using the HTTPService class. We'll start with creating the initial request.
Creating an HTTPService request To create an HTTPService request using an MXML tag, you need to use the element. In order to use it within your applications, you should specify an id attribute and a u r l for the component, as shown here:
This example creates an element with an id of xmlService that requests the external file m y f i l e . x m l . The u r l property will need to include the full path for any server-side files so that the server-side code can be processed correctly. For example, if you are generating XML content using the file m y f i l e . a s p x running in the xmlTest folder on l o c a l h o s t , use the following MXML tag:
Making the request You need to make the request for the URL specified in the tag by using the send() method of the element. You call this method and refer to the element using the id property f r o m its tag. You can optionally send variables inside this method call; you'll see that approach in the next section. To make the data available when the application first loads, it's common to call this method in the i n i t i a l i z e or creationComplete event of the tag, as shown in the example here: < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" l a y o u t = " a b s o l u t e " creationComplete="xmlService.send()">
175
CHAPTER 6 If the content doesn't need to be available when the application first loads, you could also call the send() method in response to a button click or some other event. In this case, you would add the method call inside the click handler for the button. You can see an example of this approach in the following simple event-handler function: function clickHandler(e:MouseEvent):void{ xmlService.sendQ;
} The request won't be made until the button is clicked.
Sending variables with the request You can send variables or parameters with the HTTPService request. For example, if you're requesting database content through a server-side file that collates the XML content, you might send one or more parameters to filter the results returned in the response. This approach allows you to be more flexible by using the same server-side page in different situations. There are several ways to send variables with the request. First, you can send variables at the same time that you call the send() method. If you choose this approach, you can specify an Object containing the name/value variable pairs inside the method call, as shown here: Here, the use of the curly braces indicates that you've created an Object containing a property with the name lastname and the value Ambrose. You can also can use the tag inside the tag to list the parameters. In the next code block, the tag sends the same parameter, lastname, as in the previous example:
Specifying a return type Remember that there are several different return types that you can specify for the element. These are object, array, xml, f l a s h v a r s , t e x t , and e4x. When you're working with external XML content in ActionScript 3.0, you'll use the e4x value in most cases, as this return type will allow you to interrogate the loaded content using E4X expressions. You set the returnType property for the element as shown in bold in the following code block: In this case, the code sets the resultFormat property to e4x so that you can use E4X methods with the loaded content.
Specifying a request method Parameters are always sent using HTTP GET, unless you specify something else with the method property. In most cases, you would add this property to the tag to use the POST method for the request, as you can see in the following example: You can also specify HEAD, OPTIONS, PUT, TRACE, or DELETE, but only if you set the useProxy property to t r u e and use the server-based proxy service. As I mentioned earlier, I'm not going to cover this topic because it is beyond the scope of the book.
Receiving a response When the element receives a response, it dispatches a r e s u l t event to the application to notify the application that the server response is available. If the request fails, a f a u l t event is dispatched to the application.
177
CHAPTER 6 You can specify handlers for each of these events in the element, as shown in the following example.
url="myfile.xml"
Both of these handler functions receive an event object as a parameter; however, they are different event types. The role of the r e s u l t event handler function is to process the loaded content and add it to the application interface appropriately. This handler function will receive an event that is of the type ResultEvent. The f a u l t event handler should process the passed FaultEvent. Its role is to respond so that the request for the service doesn't fail silently without the user knowing what has gone wrong. The f a u l t event handler would normally notify the user that the call has failed and provide a reason for the failure wherever possible.
It's not necessary to name the handler functions as I've done here, using the names resultHandler and faultHandler. In fact, you can use any name that seems logical to you for these functions. However, it's a handy convention to name the handler functions according to the event that they handle, because it is easier to follow the logic within the application code. If you want to see which method deals with the r e s u l t event, you can immediately look for the resultHandler function.
The final task is to access the loaded content, However, I'll deal with this topic separately, after I cover the HTTPService class.
Using the HTTPService class The HTTPService class exists within the m x . r p c . h t t p package. Although it operates in a similar way to the tag, there are some minor differences, which I'll highlight next.
Properties, methods, and events of the HTTPService class The HTTPService class has most of the same properties as the element. However, the concurrency and showBusyCursor properties from the element are not available to the HTTPService class. The only difference between the methods of the tag and class is the constructor method. The HTTPService class constructor method, HTTPServiceQ, takes only a single optional parameter, the rootURL, which is a S t r i n g value used when calculating relative references. It does not allow d e s t i n a t i o n as an optional parameter. The events of the tag and the HTTPService class are identical.
178
LOADING METHODS SPECIFIC TO FLEX
Putting it all together This section describes how to use a scripted approach with the HTTPService class properties, methods, and events. I'll show you the same tasks that we covered when looking at a tag-based approach. Again, we'll start with making the request for the service.
Creating an HTTPService request You can create an HTTPService request with the following ActionScript code: var xmlService: HTTPService = new HTTPServiceQ; xmlService. url = "myfile.xml"; The code starts by calling the constructor method without passing a rootURL parameter to create the HTTPService object. It then sets the u r l property for the service to call the external file my-File.xml. This example is equivalent to the tag-based code that you saw earlier.
Making the request To make the request, you need to use the send() method of the HTTPService class and refer it using the id assigned to the object. If the data needs to be available to the application when it first loads, it's common to call the send() method in an initializing function, as shown here: The i n i t A p p ( ) function would set any properties for the HTTPService object, including event handlers. It will usually finish by calling the send() method of the HTTPService object. You might see code similar to the following in the i n i t A p p Q function: var xmlService:HTTPServer = new HTTPServiceQ; function initApp(e:FlexEvent):void{ xmlService.url = "myfile.xml"; xmlService.addEvent Listener(ResultEvent.RESULT, xmlService.send();
resultHandler);
} As I discussed earlier, if the content doesn't need to be available when the application first loads, you might call the send() method in response to some other event. For example, you could add the call inside the click handler for a button, as you can see in the following example: function clickHandler(e:MouseEvent):void{ xmlService.send();
}
In this case, clicking the button calls the send() method.
179
CHAPTER 6
Sending variables with the request You can send parameters or variables to the request using ActionScript 3.0. As I mentioned before, you might use these parameters to filter the results returned by the request. The first approach is to add an Object containing the name/value pairs inside the call to the send() method, as you can see in the following line: xmlService.send({lastname:
'Ambrose'};
This line sends the parameter lastname and its value Ambrose with the request. Another approach is to add the parameters to an Object first. You can then assign the Object to the request property of the HTTPService class. var params:0bject = new O b j e c t Q ; params.lastname = "Ambrose"; xmlService.request = params; You can also set the contentType property for the parameters, as shown here: xmlService.contentType=
"application/xml";
You would do this if you were sending an XML object containing the parameters for the request.
Specifying a return type You can set the return type for the results of the request when scripting the HTTPService class, as shown here: xmlService.resultFormat = "e4x"; In this case, the results will be treated as an XML document that you will be able to interrogate with E4X expressions.
Specifying a request method As I've mentioned previously, parameters are always sent using HTTP GET, unless you specify something else with the method property. You can set this value in ActionScript, as shown here: xmlService.method = "POST"; This line sets the method to POST. You can find a discussion about using GET and POST in Chapter 9.
Receiving a response As you saw earlier, when the HTTPService receives a response, it dispatches a r e s u l t event. It dispatches a f a u l t event if the call fails. You can specify handlers for each of these events, as shown in the following example using the ActionScript addEventListener() method. You would probably include these method calls in a function that initializes the application, as in the example shown earlier.
180
LOADING METHODS SPECIFIC TO FLEX xmlService.addEvent Listener(ResultEvent.RESULT, resultHandler); xmlService.addEvent Listener(FaultEvent.FAULT, faultHandler); These lines of code assign two event handlers that listen for the r e s u l t and f a u l t events. Don't forget that you don't need to use the function names r e s u l t H a n d l e r and f a u l t H a n d l e r . You could use any other name that seems appropriate. Whichever approach you choose—either tag-based or using ActionScript 3.0—you'll need to access the content loaded by the request, and that's the topic of the next section.
Accessing loaded content Once a request has been successfully processed, you can access the response in the l a s t R e s u l t property of the HTTPService object. You can work with this property in the r e s u l t event handler function, or you can bind the content directly to another component.
Accessing the lastResult property directly The following lines show how you might use a r e s u l t event handler function to access the loaded content: function resultHandler(e:ResultEvent):void { //do something with e.target.lastResult
}
Notice that the function uses the ResultEvent object passed to the handler function. It addresses the t a r g e t property of this event, the HTTPService object, and accesses the l a s t R e s u l t property. If you've specified a r e s u l t F o r m a t of e4x, you can use E4X expressions to target the content within the loaded XML document. Treat the expression e . t a r g e t . l a s t R e s u l t as the root node of the XML document and build the expression from that point. You'll see this expression in the examples that follow. You can also use the l a s t R e s u l t property in binding expressions to components.
Binding the lastResult property Binding expressions with curly braces allows you to bind a property of a control directly to a value or property from a loaded XML document. You can use an E4X expression to identify which part of the document should be bound to the property of the target component. The following example shows how you might bind a property within the l a s t R e s u l t property directly to a target component property. The relevant binding expression appears in bold.
181
CHAPTER 6 This example binds the p u b l i s h e r property f r o m the response to the t e x t property of a Text control using curly braces notation. The < p u b l i s h e r > element is a child of the root element, which is equivalent to t x t L o a d e r . l a s t R e s u l t . Whenever you work with a r e s u l t F o r m a t of e4x, the l a s t R e s u l t property is equivalent to the root element in the loaded XML content. You can use much more complicated E4X expressions to target specific content. This might include longer paths and even filters. You can find out more about E4X expressions in Chapter 4 of the book. If you assign the loaded XML content to an ActionScriptXML object first, you can still use curly braces binding with properties of other controls. Before you do this, though, you need to make sure that the XML object is a bindable variable by adding the [ B i n d a b l e ] metatag above its declaration. The following example creates an XML object called loadedXML, which could be used in binding expressions: [Bindable] private var loadedXML:XML; You can then populate this variable with XML content as part of the r e s u l t event handler function. function resultHandler(e:ResultEvent):void{ loadedXML = e.target.lastResult;
} The XML object can then be bound to the target property with an E4X expression within curly braces, as shown here: The previous example would bind the XMLList within the nodes directly to the dataProvider property of the cboAuthor component.
Working through an tag example Let's work through a simple example. You'll create an application that uses the content from an XML document to populate a ComboBox control. This example won't use any scripting at all. 1. Create a new Flex project for the chapter. Add a new folder called assets to the src folder. Copy the resource file authorsAndBooks.xml to this folder. 2. In the MXML application file, create an application with the following interface. Figure 6-1 shows how the application will appear when you switch to Design view in Flex Builder.
182
LOADING METHODS SPECIFIC TO FLASH
Figure 6-1. The tag example application interface This is a very simple application that contains two Label controls, a ComboBox control, and a TextArea control. You'll populate the ComboBox with the author names, and use the TextArea to display messages and the loaded XML so you can keep track of the current content. 3. Add an tag above the element, as follows:
url="assets/authorsAndBooks.xml"
The code specifies the r e s u l t F o r m a t of e4x so you can use E4X expressions to target the content in the XML file. Feel free to open this XML document if you want to see its structure. This file is the same one we used for the Flex examples in the previous chapter. 4. The application will load the external document by calling the send() method of the element. It will do so in the creationComplete event of the application. Modify the tag, as shown here in bold: < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="xmlService.send()"> After the application is created, the send() method will request the authorsAndBooks.xml document. The contents will be returned as an ActionScript 3.0 XML object, capable of being accessed with E4X expressions. 5. You'll bind the results returned from the xmlService to the TextArea control so you can see what's loaded. Modify the tag as shown here in bold: The t e x t property of the TextArea control is bound to the l a s t R e s u l t property of the xmlService object. When the element receives a result, it will display in the TextArea. 6. The example will also use a binding expression to populate the ComboBox control. Modify the element as shown here in bold:
183
CHAPTER 6 The code sets the d a t a P r o v i d e r property of the ComboBox to an E4X expression. The element targets the loaded content using the expression x m l S e r v i c e . l a s t R e s u l t . This expression is equivalent to the root element of the loaded XML document. The application can then target the elements in the loaded content using the expression a u t h o r . authorLastName. 7. Run the application f r o m Flex Builder. Figure 6-2 shows how the completed application will appear in a web browser. The loaded content appears in the TextArea, while the ComboBox displays a list of all author last names. Author name Output Fr < publishYea N The code starts by importing the classes needed for the application. This includes FlexEvent, passed by the i n i t A p p ( ) function; ResultEvent, to determine when the request has finished; the custom class MyHTTPServiceHelper; and the X M L L i s t C o l l e c t i o n class, required for the ComboBox data provider. The code block declares a private variable for the MyHTTPServiceHelper object, called myXMLService. It also creates a variable called authorsXMLLC, which will be used as the d a t a P r o v i d e r for the ComboBox control. The element includes the i n i t A p p ( ) function, called when the interface has finished creating. This function creates a new MyHTTPServiceHelper object and assigns a r e s u l t event listener. It also calls the sendRequest() method of this object, passing the name of the file to load. You'll see an empty r e s u l t H a n d l e r ( ) function below this one, which you'll populate a little later. 12. You're now loading the switch back elements in B Problems
S Console
ComboSgiptedExarnple ;SWF;
at the stage where you can debug the application to check that you're correctly external XML document. Click the Debug button on the Flex Builder toolbar and to Flex Builder from the web browser. You should see the XMLList of all the Console view, as shown in Figure 6-5. Debug [Flex
Application]
•
E:\aip\clients\Apress-FriendsOfEd\XML
You can find the completed application files saved MyHTTPServiceHelper.as with the chapter resource files.
as
ComboScriptedExample.mxml
and
In the previous examples, you didn't send any variables with the request. We'll remedy that in the final two examples.
Passing variables with the request Earlier in the chapter, I showed you how to pass variables to the requested URL with the tag and HTTPService class. One use might be to send a parameter to a server-side file so that you can filter the content returned to the Flex application. You can also pass variables that will be used for updating the external data source. We'll work through a simple example that sends a username and password to a server-side page. I'll show how to do this with both the element and the HTTPService class. The interface in both examples will include text fields that allow the users to enter their username and password, as well as a button to submit the details. You'll also display a message from the requested file. In this case, you're going to work with a static XML document. This means that no matter what values you pass with the request, you'll see the same message from the XML document. In Chapter 9, you'll see how to integrate this approach with server-side pages. For now, these simple examples will get you started. Let's begin by working with the tag.
Using to send variables Earlier in the chapter, I showed you how to use the tag to send variables with an HTTPService request. The following code provides a quick refresher: Ambrose
191
CHAPTER 6 The element is nested inside the element and contains tags that use the variable name to surround the variable value. You'll use this approach in the first of the final two examples, so let's get started. 1. Create a new application file and name it anything that you like. 2. Create the following interface. Figure 6-7 shows how the interface should appear in the Design view of Flex Builder. < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
Figure 6-7. The example application interface The interface consists of some labels, two Textlnput controls, a Button component, and a dynamic text field. The Textlnput controls have the instance names usernamejtxt and p a s s w o r d j t x t , respectively. The Button is called l o g i n _ b t n and has the label Log in. The dynamic text field has the instance name messagejtxt. The p a s s w o r d j t x t instance has its displayAsPassword property set to t r u e . 3. Add the following tag above the opening component:
192
url="assets/message.xml"
LOADING METHODS SPECIFIC TO FLASH Notice that the code includes a closing tag, as you will add an element between the opening and closing tags. The element specifies e4x as the r e s u l t F o r m a t because the application expects an XML format for the response. You will be able to extract the message f r o m this response using an E4X expression. 4. The entries f r o m the user in the TextField controls will provide the variables that will be passed in the element. Modify the tag as shown in bold here: {username_txt.text} {password_txt.text} lisher>Friendly Boo ookxt:ookName>3iJi:es as an alternative source of transporti/fcookNamexfcook:
-I H Figure 7-13. Tracing the results property of the XMLConnector This simple exercise used the XMLConnector component to load XML content f r o m the external file authorsAndBooks.xml. The application included ActionScript to display the XML tree so you could test that the XML document loaded successfully. After you load an XML document, you'll probably bind the content to other components. You can bind the XML directly to one or more components, or you can bind to a DataSet component first. In the next section, I'll show you how to bind directly to Ul components.
Binding XML data directly to Ul components You use data binding to add the data from your XML document to one or more Ul components. This is done visually, through the Component Inspector. Visual data binding is much easier than writing ActionScript 2.0 to populate the other components. If you've worked with XML in ActionScript 2.0, you'll be aware of how convoluted the code can become when it comes to accessing content. Instead of looping through childNodes, you can configure the bindings in the Bindings tab of the Component Inspector. For example, you can bind XML directly to the dataProvider of a ComboBox or List component.
CHAPTER 6 The Bindings tab of the Component Inspector determines how data is bound to another component. You'll need to bind from the XMLConnector to a data-aware component, such as a List, DataGrid, or ComboBox. You'll need to set the direction for the binding. Bindings are often one way: the data will come out of one component—the source—and go into another—the target. This is the case where you want to display external data within Flash, without tracking updates. For example, the data could come out of the XMLConnector component into a List or Textlnput component. Sometimes you'll have a two-way binding between your components, especially where you want to be able to update your content within Flash. In this case, the content in both components is synchronized, regardless of which component makes the change. You'll see an example of this later, when we bind a DataSet component using two-way bindings in the "Putting it all together section." You need to add your bindings in the first frame of your Flash movie. They won't work on components that you add later in the timeline. The other restriction is that you can't bind components in multiple scenes in a Flash movie. Data binding is a huge topic, and I won't give it full coverage here, as that's beyond the scope of this book. Instead, I will show you some of the most important aspects so that you can create bindings using the XMLConnector component.
Adding a binding Add Binding
^^
* p rams : XML * > resuits : XML à - * ¥ allAuthors : Object publisher: String publishYear: Integer author: Array k E E M - • [n] : Object H @ authorlD : Integ authorFirstName : authorLastName : i
ill.
w
1 . 1
w
I
H
I I Use path expression
OK
I I
Cancel
~|
Figure 7-14. Selecting a property of the XMLConnector results for binding
Parameters | Bindings | Schema |
«1= CD results, all Authors, author
Figure 7-15. A binding in the Bindings tab
216
To add a binding, first select the component on the Stage and display the Bindings tab in the Component Inspector. Click the Add Binding button, which appears as a blue plus sign. You'll need to identify which property of the XMLConnector results you want to bind. If you're binding multiple values to a data-aware component, you'll usually choose an Array. You might choose a S t r i n g property if you were binding directly to the t e x t property of a Textlnput component. Select the relevant element from your schema and click OK. Make sure that you've chosen the parent of any items that you want to include within your data-aware component. Figure 7-14 shows the selection of the author Array. Choosing this element means I can access the authorFirstName and authorLastName elements. As I've selected an A r r a y element, I can bind the data to the d a t a P r o v i d e r of a data-aware component such as a List, ComboBox, or DataGrid. Once you've added the binding, you'll see it displayed within the Bindings tab, as shown in Figure 7-15. Notice that the path for the binding uses a dot notation to drill down through the elements in the structure, starting with r e s u l t s , which is equivalent to the root node of the XML document.
LOADING METHODS SPECIFIC TO FLASH The example in Figure 7-15 demonstrates binding to the author element in the a l l A u t h o r s element. This element is part of the r e s u l t s property of the XMLConnector component. After you've added a binding, you'll need to configure the other component involved in the binding, or the target for the bound data.
Configuring the binding You can use the Component Inspector to select a direction and a second component for the binding. The direction—in or out—specifies whether the data is sent out of a component or received by a component. You can also change the way that the bound data displays in the target component. Table 7-4 summarizes each of the settings in the Bindings tab and explains their purpose. Table 7-4. Settings for each binding
Setting
Purpose
direction
Specifies whether the binding sends data, receives it, or does both. Choose in, out, or in/out.
bound to
Specifies the other component or target for the binding.
formatter
A formatter to change the display within the target component. The choices are None, Boolean, Compose String, Custom Formatter, Date, Rearrange Fields, and Number Formatter.
formatter options
Lists the options available for the chosen formatter.
You'll learn more about formatters in the next examples. If you're directly binding from an XMLConnector component to another component, you would set the direction to out and select the target component instance. If you've chosen an A r r a y element f r o m the r e s u l t s , you'll need to select a component capable of displaying more than one value, perhaps a ComboBox component. You can then set the A r r a y as the dataProvider for the target component. You could also bind to the s e l e c t e d l n d e x property. You'll see how both of these bindings work in the upcoming examples in this chapter. When you click in the bound to setting, a magnifying glass icon appears at the right of the field. Click the icon to select the target component. Make sure that you have set an instance name for the target first. Figure 7-16 shows the Bound To dialog box. In Figure 7-16, I've selected a List component. Because the List component has a d a t a P r o v i d e r property, I can select this property f r o m the Schema location section of the dialog box. A d a t a P r o v i d e r is an Array, so you can assign the A r r a y from the XMLConnector results.
217
CHAPTER 6
Figure 7-16. The Bound To settings The arrows in the Bound To dialog box indicate which directions are available to each binding. In Figure 7-16, the right arrow indicates that the XMLConnector can send data to the dataProvider of the a u t h o r J L i s t component. Because the binding is one way, the List can't add a new item to the authors_xc component. The s e l e c t e d l n d e x location allows you to add a two-way binding. This means that selecting an item in the component selects the same item in the XML data. You would use the s e l e c t e d l n d i c e s item if you were selecting multiple items in the Ul component. Click OK to set the binding. If you select the target component on the Stage, you'll see that Flash has added an equivalent binding in the Bindings tab. If the data has more than one child element, you'll assign all the children to the bound component. For example, if you've bound an A r r a y directly to the dataProvider of a component, it will display a list of all values f r o m the Array, separated by commas. This is shown in a List component in Figure 7-17. 1, Alison, Ambrose, ^ b o o k s x b o o k booklD="1 m >Shopplng for profit and ph 2, Douglas, Donaldson, < b o o k s x b o o k b o o k l D = " 5 " x b o o k N a m e > O u t s t a n d i n g dinner p 3, Lucinda, Larcombe, -=:books:=-=:boo\r--Blke riding tor non-bike rlders-^bookN
Figure 7-17. Without a formatter, the complete element displays in the component. You can add a formatter to the binding to choose which element displays in the target component. For example, for the data in Figure 7-17, you could use a Rearrange Fields formatter to assign values to the l a b e l and data properties of the List component. This would allow the List to display only the name.
218
LOADING METHODS SPECIFIC TO FLASH To make this clearer, let's work through an example that binds the contents of the authorsAndBooks. xml file directly to a List component. The example will use a formatter to display the full name of each author.
Working through a binding example You'll use the file from the first exercise and bind the authorFirstName and authorLastName elements to a List component. If you didn't complete the first exercise, you can use the starter file loadAuthors.fla. 1. Open either the starter file l o a d A u t h o r s . f l a or your completed file from the first exercise. Make sure the file is in the same folder as the authorsAndBooks.xml file. 2. Drag a List component to the interface layer and size it appropriately in the Properties panel. Give it the instance name authorjist. 3. Select the XMLConnector component, display the Bindings tab in the Component Inspector, and click the Add Binding button. 4. Select the author array f r o m the XMLConnector schema and click OK. 5. Select the binding and click the magnifying glass icon in the bound to setting and add a binding to the dataProvider of the a u t h o r J L i s t component. Click OK to create the binding. 6. Change the binding direction to out. 7. Test the movie, and you'll see the complete contact element displayed in the List component. Your application should look like the image shown in Figure 7-17. You'll need to add a formatter to display only the name. 8. Select the binding and choose the Rearrange Fields formatter. This formatter will allow the application to select the authorFirstName and authorLastName elements for the l a b e l within the list. It can also add a data value at the same time. 9. Click the formatter options setting to show the magnifying glass icon. Click the icon and enter the following settings. When you've finished, click OK to apply the settings. label='
';data=authorID;
The code sets the l a b e l property of the List items to the author's full name and the data property to the a u t h o r l D attribute. The code creates the full name by joining the and elements with a space between. Notice that for the l a b e l property, you need to use angle brackets and place the expression between single quotes. You can just use the field name when assigning the data property. 10. Test the movie. You should see something similar to the screenshot in Figure 7-18. You can see the completed file saved as l o a d A d d r e s s _ B i n d i n g . f l a in your resource files.
Alison A m b r o s e Douglas D o n a l d s o n Lucinda Larcombe Saul Sorenson
Figure 7-18. The List component showing the author's full name from the XML document
219
CHAPTER 6 In this exercise, you bound the data from an XMLConnector component directly to a List component. The example used a Rearrange Fields formatter to display the author's full name from two elements within the loaded XML tree. It also assigned the a u t h o r l D attribute to the data property.
The Rearrange Fields formatter transforms the content from the XMLConnector before it displays in the target component, the List. Because it creates a new Array of objects from the original Array in the XML tree, you can use it only with fields that are arrays— in this example, the elements. You create the new Array by using a template. You can refer to the original field by using its name or, as you did in this example, by creating a string entry containing a mix of text and fields. In this case, you had to write the field names as XML elements with opening and closing angle brackets.
V
Your code needs to assign the template to a property on the bound component. In the example, the code assigned the template to the label property. It also assigned the authorlD attribute to the data property of the List component.
You can extend the example so that when you click the name, the author's details will display in other components. You'll do this by adding more bindings.
Extending the binding example In this exercise, you'll add multiple bindings so that when you choose an item from the List, the details display in Textlnput and TextArea components. If you didn't complete the previous exercise, you can use the starter file loadAddress_Bi.nding.-Fla f r o m the resources. 1. Open either the starter file l o a d A d d r e s s _ B i n d i n g . f l a or your completed file from the previous exercise. Again, the file should be in the same folder as authorsAndBooks.xml. 2. Set up the interface as shown in Figure 7-19. I've called the books List component booksJList. The year Textlnput component has the instance name y e a r j t x t . The cost Textlnput component is named cost t x t .
m Books
Year Cost
Figure 7-19. The Flash interface
220
LOADING METHODS SPECIFIC TO FLASH 3. Select the XMLConnector component and add a new binding to the books element. Set the direction to out and bind it to the dataProvider property of the booksJList component. You'll see an additional setting at the bottom of the Bindings tab called Index for 'author' with a value of 0. This setting shows which value f r o m the array to display in the List component. The application will bind this to the s e l e c t e d l n d e x f r o m the a u t h o r J L i s t component. 4. Click the magnifying glass icon in the Index for 'author' setting to bring up the Bound Index dialog box. Uncheck the Use constant value check box. Select the a u t h o r J L i s t component and choose the s e l e c t e d l n d e x property. This tells the application that the values in the books_ l i s t component depend on the item chosen f r o m the a u t h o r J L i s t component. Figure 7-20 shows the settings for the Bound Index dialog box.
Figure 7-20. The settings for the books binding 5. Test the movie and check that the booksJList component populates when you select an author name from the List component. You'll notice that the entire element displays, so the example will need to use another formatter. 6. Add the Rearrange Fields formatter for this binding and add the following formatter option: label=bookl\lame; data=bookID; 7. Test the movie again. You should see the book name in the booksJList component when you select an item from the a u t h o r J L i s t component. 8. You might notice that when the application first appears, the word undefined shows in the booksJList component. This entry occurs because no index is selected in the a u t h o r J L i s t component. You can fix this problem by removing the item after the XMLConnector finishes loading. Modify the x m l L i s t e n e r . r e s u l t function on the actions layer as shown in bold here: xmlListener.result = -function(e:0bject):Void { trace(e.target.results); boo ks_li st.removeAl1()j
};
When you test the movie, you'll see nothing in the booksJList component until you select an item from the a u t h o r J L i s t component.
221
CHAPTER 6 9. Add another binding to the book year as follows: a. Select the XMLConnector component and add a binding to the bookPublishYear element. b. Set the direction to out and bind the data to the y e a r _ t x t component. c. You'll see two Index settings, one for each List component. Set the Index for 'author' to the s e l e c t e d l n d e x property of the a u t h o r J L i s t component. Set the Index for 'book' to the s e l e c t e d l n d e x property of the bookJList component. Figure 7-21 shows the settings.
Parameters | Bindings | Schema | ~ CD results, all Authors, author CD results. all Authors. author. [n]. books. book CD results. all Authors. author. [n]. books. book, [n]. bookPublishYear
Name
Value
direction
out
bound to
year_txt:text
formatter
none
formatter opti... Index for 'aut... author list: selectedlndex Index for 'book' books list: selectedlndex
Figure 7-21. The Bindings settings for the bookPublishYear binding 10. Add a similar binding for the bookCost element. In order to display the cost to two decimal places, you'll need to add a Number Formatter with a precision of 2 decimal places. Figure 7-22 shows the settings for this binding.
Parameters | Bindings | Schema | ~ CD results, all Authors, author CD results. all Authors. author. [n]. books. book CD results. all Authors. author. [n]. books. book, [n]. bookPublishYear CD results. all Authors. author. [n]. books. book, [n]. bookCost Name
Value
direction
out
bound to
cost txt:text
formatter
Number Formatter
formatter opti... {precision: "2"} Index for 'aut... author list: selectedlndex Index for 'book' books list: selectedlndex
Figure 7-22. The Bindings settings for the bookCost binding
222
LOADING METHODS SPECIFIC TO FLASH 11. Test the movie. You should be able to select an author and book, and see the publish year and cost, as shown in Figure 7-23.
Figure 7-23. The completed application interface You can find my completed example saved as loadAuthors_multipleBindi.ng.-Fla with the chapter resources. In the previous examples, you've seen how easy it is to load XML content into an XMLConnector component and bind it directly to one or more components. You created a very simple application that displays details of an author's books, without writing much ActionScript at all. However, if you need to update the XML content, things become a lot more complex, as you need to introduce other data components into the mix.
Using the DataSet component You've seen how to bind XML data directly to other components. This approach works well if you have data that you don't need to update. However, if you want to make changes in Flash, you'll need to work with DataSet and XUpdateResolver components to keep track of your updates. Like the XMLConnector, the DataSet has no visual appearance, which means you can place it anywhere in your Flash movie. You use the DataSet component to store and organize the data before you bind it to other components. You can also use the DataSet to track changes that you make in the other components. Remember that Flash can't alter external content and requires server interaction for any updates, so you'll also need to send these updates to a server-side file for processing. Once the updates are complete, the DataSet sends information about the updated data to an XUpdateResolver component in a deltaPacket. The XUpdateResolver processes the deltaPacket and generates an xupdatePacket for use by server-side files.
223
CHAPTER 6
Creating bindings with a DataSet component You'll bind data from an XMLConnector component to a DataSet component. This is necessary so that the DataSet can keep track of any changes made by other components. The process is as follows: 1. Configure the XMLConnector. 2. Bind the XMLConnector to a DataSet. 3. Apply two-way bindings from the DataSet to the other components. The two-way bindings ensure that the DataSet always contains the latest data. 4. Add bindings between the DataSet and XUpdateResolver. You normally bind an Array property of the XMLConnector to the dataProvider of the DataSet. There are other properties that you can use for binding. In addition, you can bind to the deltaPacket—an XML packet describing changes to the data. This binding tracks updates. You can also bind to the items in the component or to the selectedlndex. When you add bindings from the DataSet to other components, you must select an in/out direction so that the components will inform the DataSet of any changes that a user makes. For list-based components, you'll need to bind both the dataProvider and selectedlndex properties to synchronize the DataSet and component. You should also create fields in the schema of the DataSet so that the schema matches the exact structure of the results from the XMLConnector component. If you don't do this, the two components won't be identical, and you may have difficulty generating updates later on. Make sure that when you specify the name and data type of the field they are the same as in the XMLConnector schema; that way, you ensure that the two components contain exactly the same content. This is necessary so that you'll be able to update the data correctly. To capture any changes made to the data, you'll need to add an XUpdateResolver component that binds to the DataSet component. This component keeps the data in Flash consistent with an external data source. The resolver translates information about changes from a DataSet component into a format that the external data source understands. The relationship between the DataSet and XUpdateResolver is a little complicated. The DataSet monitors any user changes in the components and stores them in a deltaPacket. When you're ready to process these changes, the DataSet sends the deltaPacket to the XUpdateResolver. The resolver converts the deltaPacket into an xupdatePacket that you can send to an XMLConnector. This XMLConnector sends the xupdatePacket to a server-side file where the updates are processed. The server-side file returns an updateResults packet to the XMLConnector. This packet may contain updated values for components, such as those from ID fields. Figure 7-24 shows the process, reading from left to right. The XUpdateResolver uses XUpdate statements to describe changes that you've made to the data. At the time of writing, XUpdate was a working draft from the XML: DB Working Group. You can find out more about XUpdate at http://xmldb-org.source-Forge.net/xupdate/.
224
LOADING METHODS SPECIFIC TO FLASH
Figure 7-24. The update process using a DataSet and XUpdateResolver To make sure that you track all of the changes to your data, you'll need to add two bindings: one for the deltaPacket and one for the xupdatePacket. The first binding occurs between the deltaPacket of the DataSet and the deltaPacket of the XUpdateResolver. The deltaPacket is generated by the DataSet component to summarize changes made to the XML content. You'll need to set the direction to out for the DataSet and in for the XUpdateResolver. In other words, the DataSet sends the changes in the deltaPacket to the XUpdateResolver component. You'll need to add another binding to the XUpdateResolver so that the xupdatePacket is sent out to a second XMLConnector. This XMLConnector sends the xupdatePacket to a server-side file for processing. The XMLConnector will also receive an updateResults packet f r o m the server-side file after processing. The server-side page can use the updateResults packet to send additional data to Flash, such as the primary key values of new entries. One crucial step in the process is setting the encoder for the XUpdateResolver deltaPacket in the Schema tab of the Component Inspector. You'll need to choose the DatasetDeltaToXUpdateDelta encoder and specify the rowNodeKey in the encoder options. The rowNodeKey is an XPath statement that identifies the path to the rows of data. It's a little like the primary key for a database. The XPath statement also contains a predicate that links the path to the relevant data in the DataSet. The following code shows the structure of this setting: XPathStatement/rowNode[keyFieldName='?DSKeyFieldName'] The setting includes an XPath expression that identifies the path directly to the row node of the data. The predicate, within square brackets, includes the key field from the schema, an equal sign, and the key field f r o m the DataSet, prefaced by a question mark. The DataSet key field name is usually the same as the schema key field. The text to the right of the equal sign is surrounded by single quotes. When Flash creates the xupdatePacket, it will convert the single quotes to the entity '. It's critical that you write this path correctly; otherwise, you won't be able to generate the correct XUpdate statements in your xupdatePacket. You need to trigger the DataSet to create the deltaPacket by calling the applyUpdates method. The DataSet then generates the deltaPacket containing the changes to the data. The DataSet sends the deltaPacket to the XUpdateResolver, where the contents are converted into XUpdate statements and added to an xupdatePacket.
225
CHAPTER 6 The following code shows the structure of an xupdatePacket: Alan"); The first argument to the method indicates what you're replacing. Here, it's the element. The second argument provides the replacement content—in this case, o u t hor F i r stName>Alan . You'll notice that the second argument requires the opening and closing tags to be included. Using the r e p l a c e ( ) method is obviously more cumbersome than adding a simple assignment that uses an equal sign. However, this method allows you to specify a different element structure to use as a replacement. It would be possible to replace the element with a completely different set of elements.
You can also use the r e p l a c e ( ) method to change an element name or completely modify the XML structure of the specified element. This use of r e p l a c e ( ) is covered in the "Editing content" section later in the chapter.
Modifying attribute values is just as easy. For example, we can change the value of the authorlD attribute of the first author using the following line: authorsXML.author[0] .(®authorID="99"; Tracing the value of the first element shows the following content: We've successfully changed the value of the attribute. It is possible to change more than one value at a time by working through the entire collection. For example, you could modify more than one element or attribute by looping through the collection and treating each item individually. For example, to add the number 10 before each of the current authorlD attribute values, you could use the following code: f o r each(var aXML:XML in authorsXML.author) { aXML.gauthorlD = "10" + aXML.gauthorlD;
} This example uses a f o r each statement to iterate through all of the elements in the XML object. You can then treat each individually. Because the code treats the elements separately, each element is an XML object in its own right.
236
MODIFYING XML CONTENT WITH ACTIONSCRIPT 3.0 Tracing the XML tree for this example shows the following values for each of the elements:
The code changes all attributes to include the number 10 at the start. Note that, unlike what is shown in the preceding code block, the opening elements won't all appear next to each other when you run the example. You can find all of these examples saved in the file c h a n g i n g V a l u e s . f l a and ChangingValues.mxml with your other chapter resources. Unfortunately, it's not quite as easy to modify XML element and attribute structures. In the next section, you'll see how to use methods of the XML class to make changes to XML structures.
Adding, editing, and deleting XML content Chapter 3 demonstrated how to use several of the methods of the XML class to modify the structure of an existing XML object. I've summarized these methods in Table 8-1. Table 8-1. Methods of the XML class for modifying XML content
Method
Description
appendChild(child)
Adds the specified child node at the end of the child nodes collection of the identified node
Inserts a new child node after the identified child node Inserts a new child node before the specified child node Adds the specified child node at the beginning of the child nodes of the identified node
value)
Replaces a specified property, perhaps an element or attribute, with the provided value Replaces children of an XML object with specified content
We'll now work through each of these methods in a little more detail so you can see how they work. A little later in the chapter, in the "Working through a modification example" section, you will use some of the methods in a practical application. We'll start by examining the appendChild() method.
237
CHAPTER 6
Using appendChildO The appendChild() method adds a new child node at the end of the current collection of child nodes. It takes a single argument—the child to append—and adds it as the last child element. Here is an example: var newAuthor:XML = Sas Dacobs authorsXML.appendChild(newAuthor); The code starts by creating a new XML object called newAuthor. It then assigns the details of the new node to this object, including the structure and values of elements and attributes. The last line of the code calls the appendChild() method from the root element authorsXML. It passes the new XML object to the method. Tracing the authorsXML object shows that the new element is added as the last child of the element. The end of the XML object follows: Sas Dacobs The appendChild() method can add the new child element at any point in the XML object. It doesn't need to refer to the root element. The following code block shows how to add a new child book to the second element: var newBook:XML = Hearty Soups 2008 ; authorsXML.author[l].books.appendChild(newBook); After running this code, the list of books by this author includes the following: Outstanding dinner parties 2003 Cooking f o r fun and profit 2007 Hearty Soups 2008
238
MODIFYING XML CONTENT WITH ACTIONSCRIPT 3.0 The new book appears last in the list of all books by this author. Instead of creating the new element separately, you can also add it directly as an argument to the appendChild() method. In the following example, the new XML element appears inside the call to the appendChildQ method. authorsXML.appendChild( *-Sas *-Dacobs); You can see that this approach produces some unwieldy code. My preference is to create the child element separately, as it makes the code easier to read. It's also possible to set the child elements of the new XML object as properties using dot notation. You can see this approach in the following example: var newAuthor:XML = ; newAuthor.authorFirstName = "Sas"; newAuthor.authorLastName = "Jacobs"; Instead of using XML structures, the code defines the child elements as properties. This approach creates the same result, and you'll use it in examples later in the chapter.
Using prependChildO The prependChild() method works in much the same way as appendChildQ, except that it adds the new child element as the first child of the selected parent. Using this method moves all of the existing child elements to a position one ahead of their original position. We could use the same new element and add it as the first child with the following code: var newAuthor:XML = Sas Dacobs ; authorsXML.prependChild(newAuthor); If you add this example and trace the authorsXML object, you'll see the new element appearing as the first child of the element. The existing elements appear afterward.
Copying a node In the previous examples, we created the new node by assigning its value directly to an XML object. You also saw that it's possible to add the children with dot notation instead of writing them to the XML object. Another approach is to use the copy() method to duplicate an existing node. Once you've replicated the structure, you can change the values and then insert the copied element using the appendChildQ or prependChildQ method. Here's an example of this approach:
239
CHAPTER 6 var cookbookXML:XML = authorsXML.author[0].books.book[0].copy(); cookbookXML.gbooklD = "10"; cookbookXML.bookName = "Hearty Soups"; cookbookXML.bookPublishYear = "2008"; authorsXML.author[l].books.prependChild(cookbookXML); This example creates a new XML object by calling the copy() method on the first book of the first author. Any element would do here; it's only the structure that interests us. The next three lines assign the new values to the copied element structure. The final line calls the prependChildQ method to add the new element as the first child of the element for the second author. Running the code sample produces the same output that you saw earlier. If you want to add a new element that has a complex structure, you'll probably find that using the copy() method will be quicker than the previous approaches. It is likely to take more work to create the element structure and insert it with appendChild() or prependChild() than it is to copy the structure and modify the values.
Inserting a child node Both the i n s e r t C h i l d A f t e r Q and i n s e r t C h i l d B e f o r e Q methods insert a new child node at a specific place in the XML object. You could use these methods to add a new child node at a position other than as the first or last child. The difference between the two methods is obvious from their names. The i n s e r t C h i l d A f t e r Q and i n s e r t C h i l d B e f o r e Q methods take the same two arguments. The first argument is the position at which to insert the new element. The second argument is the new child element to insert. Here's an example showing the i n s e r t C h i l d A f t e r Q method: var newAuthor:XML = Sas Dacobs ; authorsXML.insertChildAfter(authorsXML.author[l],
newAuthor);
In this example, the code adds the new element after the second author. If you add the code to the sample files, the new author will appear after Douglas Donaldson and before Lucinda Larcombe. We could achieve the same result using the following i n s e r t C h i l d B e f o r e Q method, as follows: authorsXML.insertChildBefore(authorsXML.author[2],
newAuthor);
This example inserts a new element before the current third author. It will move the existing third and later elements one position forward, so that the current third element becomes the fourth and so on. Using the i n s e r t C h i l d B e f o r e Q method with any element at index 0 is equivalent to using the prependChildQ method. It adds the new element as the first child. Similarly, using the i n s e r t C h i l d A f t e r Q method and specifying the index of the last child element is equivalent to using appendChildQ. It adds the new element as the last child.
240
MODIFYING XML CONTENT WITH ACTIONSCRIPT 3.0
Editing content The r e p l a c e ( ) method works a little differently from the other methods. It allows you to change the structure of the XML content by replacing one XML element with entirely different content. It's up to you whether you preserve the existing XML structure. The r e p l a c e ( ) method takes two arguments. The first is the property to replace. You can express this argument as the S t r i n g name of an element or attribute. It can also be provided as the index of a child element. The second argument is the replacement content. You need to provide this argument as an XML object. You could use the r e p l a c e ( ) method to replace the first element with a element, as shown here: authorsXML.replace(0,
"FOE");
Here, we've specified the first child of the authorsXML object by providing the index 0. This number equates to the first element in the XML object. Its replacement is an entirely new element name. The following example shows how to use a S t r i n g value for the element that will be replaced. authorsXML.author[0].replace("books",
"");
Here, the code replaces the first author's element with an empty element. Running the code and tracing the output produces the following change to the first author: Alison Ambrose We could also replace the entire contents of the first element, as shown here: authorsXML.author[0].replace("*", *-"Alan"); The code uses the wildcard operator * to specify that all children of the first element are to be replaced. Running this example produces the following result, where there is a single child element for this element: Alan However, you couldn't use the following code to provide a replacement value: authorsXML.author[0].replace("*", *»"Alan *-Amberson");
241
CHAPTER 6 If you tried to use this code, you would get an error message indicating that the document markup was not well-formed. This code is invalid because the replacement value is not a valid XML object. Instead, it contains two elements that aren't inside a single root element. If the objective were to replace the and elements in the first element, you would need to use the following approach: authorsXML.replace(o, *-"Alan *-Amberson"); You could also add the first child only using the r e p l a c e ( ) method, and then call the appendChild() method to add the second element afterward.
Using setChildrenO The s e t C h i l d r e n ( ) method replaces all of the children of the specified element with the content passed to the method. You can pass either an XML or XMLList object to the method. This previous example: authorsXML.author[0].replace("*", *»"Alan") is equivalent to the following line: authorsXML.author[0].setChildren( *-Alan); In the previous section's example, you couldn't pass an XMLList argument to the r e p l a c e ( ) method. That invalid example could be replaced with the following valid example: authorsXML.author[0].setChildren *»(Alan Amberson);
Deleting an element You can delete content by using the d e l e t e ( ) ActionScript operator. The following example removes the first element from the authorsXML object: delete(authorsXML.author[0]); If you test this line, the first author in the XML tree becomes Douglas elements move down one position.
Donaldson. All
Note that d e l e t e ( ) is not specifically a method of the XML object. Rather, it is an ActionScript 3.0 operator that can be used in circumstances other than working with XML content.
242
MODIFYING XML CONTENT WITH ACTIONSCRIPT 3.0 You can find the Flash and Flex examples saved as m o d i f y i n g S t r u c t u r e . f l a and M o d i f y i n g S t r u c t u r e . mxml respectively with the chapter resources. Don't forget that you will need to debug the Flex application to view the results of the t r a c e ( ) actions in the examples.
Modifying element names and namespaces One area that we haven't yet touched on in any detail is how to make modifications to the names of elements and attributes. We also haven't looked at how you can make changes to namespaces. Remember that a namespace associates an element with a particular URI. It allows you to ensure that each element and attribute name in an XML document can be uniquely identified, even if you have more than one with the same name. Let's start by looking at namespaces. The examples in this section use a slightly different XML document, authorsNS.xml. Make sure you modify your code to load this file instead of authors.xml. For the Flex example, make sure you add the file to the assets folder in your Flex project as well.
Adding a namespace You can add a namespace to an XML object using the addNamespaceQ method. The method receives the namespace that you want to add as an argument. You provide the namespace either as a Namespace object or as a OName object. When you load the new XML file, you'll see that the root element of the new authorsXML object contains two namespaces. The following example shows how to add a third namespace to the root element: var ns:Namespace = new Namespace("foe", " h t t p : / / w w w . f o e . c o m / n s / " ) ; authorsXML.addNamespace(ns); The code starts by creating a new Namespace object with the prefix foe that references the URI http://www.foe.com/ns/. It then uses the addNamespaceQ method to add the namespace attribute to the root element. After running this example, you should see the following root element: The code adds the third namespace listed in this root element. Adding a namespace to the root element means that it's available to all other child elements. However, you don't need to add the namespace to the root element. You can add it anywhere in the XML document tree, as shown here: authorsXML.author[o].books.addNamespace(ns);
243
CHAPTER 6
This line of code would add the namespace to the element of the first author, indicating that it is within that namespace: It's also possible to determine the namespace from an existing element and apply it to another element. The author Saul Sorenson has the namespace http://www.saulsorenson.com/ns/ in his element. You can see it in the following line:
In the next example, we'll refer to this namespace and add it to the second element as well. var ss:Namespace = authorsXML.author[3],namespace("ss"); authorsXML.author[l].addNamespace(ss); In this example, the object ss refers to the namespace prefixed with ss in the third element. The code adds this namespace to the second element using the addNamespace() method. Tracing the XML content shows the following opening element for the second author:
The ss namespace appears as a new namespace for this element.
Removing a namespace You can remove a namespace from an existing element with the removeNamespace() method. Let's see an example. The root element of the authorsNS.xml file contains two namespaces, as shown here: These elements have the prefixes sj and aa. We'll remove the namespace prefixed with aa with the following code: authorsXML.removeNamespace(aa); Running the example and displaying the XML object shows only one namespace remaining in the root element.
Again, you can remove the namespace from elements other than the root element. We can remove the namespace declaration from the last element using the following code: var ss:Namespace = authorsXML.author[3].namespace("ss"); authorsXML.author[3].removeNamespace(ss);
244
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
If you used this code and traced the XML object, this element would appear as follows: The element doesn't contain any namespaces.
Setting the namespace The setNamespaceQ method sets the namespace associated with an XML object. You could use this to apply the namespace from one XML object to another and set it as the default namespace. Consider the following code block: var myXML:XML = Hello w o r l d < / g r e e t i n g > ; var aa:Namespace = authorsXML.namespace("aa"); myXML.setNamespace(aa); trace(myXML.toXMLStringQ); This block of code creates a new XML object called myXML with some simple content. The second line determines the namespace associated with the aa prefix in the authorsXML XML object and stores it in a Namespace object called aa. The third line calls the setNamespaceQ method of the myXML object, passing the aa namespace as an argument. When you view the modified XML tree for the myXML object, you see the following element: Hello world The setNamespaceQ method adds the aa namespace to this XML object. It also sets aa as the default namespace by adding the prefix aa to the root element .
Note that I had to use the toXMLStringQ method here to display the XML content, as the myXML object contains only simple content. If I used t o S t r i n g Q instead, I would have seen only the text Hello world.
You can find all of these examples saved in the modifyingNamespaces.-Fla and ModifyingNamespaces. mxml files. As well as working with namespaces, you can also change the names of elements and attributes. We'll continue working with the authorsNS.xml file for the next set of examples.
Changing the local element name You can change a local element name or attribute using the setLocalNameQ method of the XML object. You need to pass the new element name when calling this method. The method doesn't change the prefix in a qualified name. We could use the following line of code to change the name of the root element of the authorsXML object to a u t h o r L i s t : authorsXML.setLocalName("authorList");
245
CHAPTER 6
Applying this code results in the following newly named root element: o u t h o r L i s t xmlns:sj="http://www.sasjacobs.com/ns/" xmlns:aa="http://www.alisonambrose.com/ns/"> In addition to the root element, you can also change the names of other elements farther down the document tree. The following example changes the first author element name from to : authorsXML.author[0].setLocalName("myAuthor"); Viewing the XML document tree shows that the element is renamed to , as shown in the following line: The setLocalNameQ method also works to change the name of attributes, as shown in the following example: aut horsXML. author [0 ] .@>aut hor ID. set LocalName("myID"); Introducing this change rewrites the first author element, as shown here:
Changing the qualified element name You can also change the qualified name of an element or attribute using the setNameQ method. Again, the method receives a S t r i n g argument indicating the new name to use for the element. If you apply this method to an unqualified element, the result is the same as applying the setLocalNameQ method. For example, the following line produces the same output as calling the setLocalNameQ method: authorsXML.author[0].setName("myAuthor"); In both cases, you end up with the element , as shown here: The difference between the setLocalNameQ and setNameQ methods will be apparent with an example that includes a namespace. The third author in the XML document, Lucinda Larcombe, has the following opening element: < l l : a u t h o r authorID="3" xmlns:11="http://www.lucindalarcombe.com/ns/"> Notice that the element name author is qualified with the prefix 11, which refers to the namespace http://www.lucindalarcombe.com/ns/. We can change only the author portion of the element name with the setLocalNameQ method, as shown in these lines of code:
246
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
var ll:Namespace = *»new Namespace("http://www.lucindalarcombe.com/ns/"); authorsXML.il::author[0].setLocalName("myAuthor"); Notice that we needed to prefix the element with the namespace 11, as shown in the XML document. We also needed to use the :: operator to indicate that the prefix belonged to the author element. Because this is the only element with the 11 prefix, we referred to it using an index value of 0:11: : a u t h o r [ 0 ] . Running this example produces the following element for this author: The prefix 11 remains in the element, but the local name of the element is changed from author to myAuthor. If we use the setNameQ method instead, we'll see a different result. The following line of code uses the setNameQ method on the same element. Again, the element name is qualified with the prefix 11. authorsXML.11::author[0].setName("myAuthor"); Applying this code, you should see the following change to the element: The element is no longer qualified by the prefix 11. However, the namespace declaration remains. These examples demonstrate that you should be very careful with the setNameQ and setLocalNameQ methods when working with qualified element names! You can find all the examples referred to in this section saved in the files modifyingNames.-Fla and ModifyingNames. mxml. Now that we've covered the ways that you can modify an XML document, let's work through an example so you can see some of these concepts in action.
Working through a modification example We'll work through an example that demonstrates how to modify an XML tree structure. We'll use the resource file authorsAndBooks.xml, which you've seen in other chapters. In this example, we'll load the contents of the document and display a list of authors in a ComboBox and their books in an editable DataGrid. We'll use the application to add, modify, and delete book details for the selected author. We'll use XML class methods to modify the XML object to keep it consistent with the changes made in the interface. In the Flash example, I'll show a simplistic version, using procedural code. The Flex version uses class files.
247
CHAPTER 6
Working in Flash Here are the instructions to set up the Flash example: 1. Open the starter file a u t h o r B o o k E d i t o r . f l a in Flash. Figure 8-1 shows the interface. The application will populate the ComboBox with authors from the loaded XML object. It will show the books for each author in an editable DataGrid below the author name. A TextArea control will show the contents of the XML object. A user can add details of a new book, modify a row in the DataGrid, or select a row in the DataGrid to delete a book.
Authors and Books
New book
i n : i )i name Boohs
Name Detete selected boon
F i g u r e 8 - 1 . The application interface
2. Create a new layer called actions. Open the Actions panel using the F9 shortcut and add the following code to load the external XML document. If you've worked through the previous exercises, there's nothing new in this code, but I'll explain it after the listing. import f l . d a t a . D a t a P r o v i d e r ; var authorsXML:XML; var authorList:XML; var booksList:XML; var request:URLRequest = new URLRequest("authorsAndBooks.xml"); var loader:URLLoader = new URLLoaderQ; initAppQ; stop(); This script block starts by importing the DataProvider class, which the application will use to populate both the ComboBox and DataGrid controls. The code creates three XML objects. The first is authorsXML, which will store the complete XML tree from the loaded content. The second XML object, a u t h o r L i s t , will populate the ComboBox. The third XML object, booksList, will store the books for each author and populate the DataGrid.
248
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
The code then creates a URLRequest object called request and a URLLoader object called loader. The URLRequest requests the authorsAndBooks.xml file and assumes that the XML document is in the same folder as the Flash application. The second-to-last line calls a function named i n i t A p p Q to set up the application. You'll create this function in the next step. The code block finishes with a stopQ action. 3. The i n i t A p p ( ) function sets up the application and loads the external XML document. Add the following code at the bottom of the Actions panel. It contains two other functions, which I'll explain after the listing. function initApp():void { loader.addEvent Listener(Event.COMPLETE, completeHandler); authors_cbo.labelFunction = getFullName; loader.load(request);
} f u n c t i o n completeHandler(e:Event):void { authorsXML = XML(e.target.data); a u t h o r L i s t = new XML(authorsXML.authors); authors_cbo.dataProvider = new D a t a P r o v i d e r ( a u t h o r L i s t ) ; t r e e j t x t . t e x t = authorsXML. toXMLStringQ;
}
function getFullName(item:Object):String{ r e t u r n item.authorFirstName + " " + item.authorLastName;
First, the i n i t A p p Q function adds an event listener to the loader object. The listener will respond when the loader dispatches the complete event by calling the function completeHandlerQ. In other words, this handler function will process the loaded content to make it available to the application. The i n i t A p p ( ) function also sets the labelFunction property for the authors_cbo control so that it can display the full name of each author. The function finishes by calling the l o a d ( ) method of the loader object to request the external XML document. The completeHandlerQ function receives an Event as an argument and uses the t a r g e t , data property of this Event object to locate the loaded data. The first line casts this data as an XML object, assigning it to the authorsXML object. You need to do this because the content is loaded as S t r i n g data. The function creates an XML object containing the list of authors by targeting the element in the XML document with the E4X expression authorsXML.authors. The third line of the completeHandlerQ function sets the dataProvider property for the ComboBox control to this XML object. The labelFunction for the ComboBox will format the appearance of the data in the ComboBox. The final line displays a S t r i n g representation of the loaded XML content in the TextArea component called t r e e j t x t . The getFullNameQ label function should be familiar to you from the earlier examples. It creates the full name by locating the first and last names of the author in the and elements. It joins these elements with a space in between.
249
CHAPTER 6
4. At this point, the application has loaded the XML document and populated the ComboBox control. Test the movie and check that the external XML document loads successfully. Figure 8-2 shows how the interface should appear at this point. You should see the TextArea populated with the loaded XML document and the ComboBox displaying the first author name, Alison Ambrose.
aiithorBookEditor_deleteme.5wf File
View
Control
Debug
Authors and Books Author name Books
New book Name
[ Alison Ambrose Delete selected book
XML t r e e Friendly Books 2QQ8 Shopping for profit and pleasure
,
Figure 8-2. Testing that the application loads the external XML document
5. The application now needs to load the books for the author displayed in the ComboBox. When the application first starts, it will display the first author's books in the DataGrid to match the value initially shown in the ComboBox. When the user selects a different author, the books shown will change. To accomplish this, the application needs to detect when the selection in the ComboBox changes, and then locate the element for the selected author. Add the following line to the i n i t A p p ( ) function to detect a change in the ComboBox component: authors_cbo.addEvent Listener(Event.CHANGE,
changeHandler);
Each change in the selected value in the ComboBox calls the changeHandler() function. Add the following changeHandler() function at the bottom of the Actions panel: f u n c t i o n changeHandler(e:Event):void { var a u t h o r I n d e x : i n t = e . t a r g e t . s e l e c t e d l n d e x ; loadBooks(authorlndex);
}
250
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
The changeHandler() function receives an Event as an argument. You can determine the selectedlndex from the t a r g e t of this event, which is the ComboBox control. The function assigns the selectedlndex value of the ComboBox to the authorlndex variable. The number will be the same as the author index from the XML object, as both are zero-based. The function finishes by calling another function, loadBooks(), passing the value of the selectedlndex property. The loadBooks() function will populate the DataGrid component. 6. Add the loadBooks() function shown here to the actions layer: function loadBooks(authorIndex:int):void { var booksDP¡DataProvider; booksList = new XML(authorsXML.authors.author[authorIndex].books); booksDP = new DataProvider(booksList); books_dg.dataProvider = booksDP;
} Each time the user selects a new author, the loadBooks() function will locate that author's books and use them to populate the DataGrid. This function starts by declaring a DataProvider object called booksDP, which the application will use to populate the DataGrid. It then locates the element using the E4X expression authorsXML.authors.author[authorIndex] .books. Notice that the E4X expression includes the authorlndex passed from the changeHandler() function. The loadBooks() function sets the returned XMLList as the source for the booksList XML object. It then assigns the booksList object to the DataProvider object. Finally, the bookDP object is assigned as the dataProvider for the DataGrid. 7. If you tested the application now, you wouldn't see any books populated initially. That's because you need to add another call to the loadBooks() function, which runs when the XML content first loads. This call will allow you to see the first author's books. Add the line shown in bold to the completeHandlerQ function: f u n c t i o n completeHandler(e:Event):void { authorsXML = XML(e.target.data); a u t h o r L i s t = new XML(authorsXML.authors); authors_cbo.dataProvider = new D a t a P r o v i d e r ( a u t h o r L i s t ) ; loadBooks(o)j t r e e j t x t . t e x t = authorsXML. toXMLStringQ;
} Once the content is loaded, the application will call the loadBooks() function, passing the first author's index of 0 as an argument. 8. Test the application again and check that the books for the first author appear when the application first loads. You also need to test that you can see other authors' books when you change the selection in the ComboBox. Figure 8-3 shows the interface when the application first loads.
251
CHAPTER 9
"ft
authorBookEditor_deleteme.swf
File
View
Control
Debug
N e w book
Authors and Books A u t h o r name
[ Afeon Ambrose
-
—
Name
| Delete sctectcd book
bookName
bookPubfehYet
bookID
Shopping tor pi
2WZ
1
bookCost i4.sy
Fishing tips
1999
4
23.50
X M L tree Friendly Books 200& outhorFirslfi arne>Afeon Ambrose Shopping for profit and pleasure
Figure 8 - 3 . Loading the DataGrid
9. The DataGrid doesn't look too good. The column headings are the default field names, and the book titles are cut off. You can fix the way it looks by calling another function, setupDataGridQ, which formats the DataGrid's appearance. Add the following function call to the end of the completeHandlerQ function. The new line appears In bold. f u n c t i o n completeHandler(e:Event):void { authorsXML = X M L ( e . t a r g e t . d a t a ) ; a u t h o r L i s t = new XML(authorsXML.authors); authors_cbo.dataProvider = new D a t a P r o v i d e r ( a u t h o r L i s t ) ; loadBooks(o); setupDataGridQ j t r e e _ t x t . t e x t = authorsXML. toXMLStringQ;
} You also need to add the setupDataGridQ function to your actions layer. f u n c t i o n setupDataGridQ:void { books_dg.columns = [ " I D " , "Name", "Publish y e a r " , " C o s t " ] ; books_dg.columns[0].width = 50; books_dg.columns[l].width = 200; books_dg.columns[2].width = 50; books_dg.columns[3].width = 50; books_dg.columns[0].dataField = "bookID"; books_dg.columns[l].dataField = "bookName"; books_dg.columns[2].dataField = "bookPublishYear"; books_dg.columns[3].dataField = "bookCost"; books_dg.columns[0].editable = f a l s e ;
} 252
M O D I F Y I N G X M L C O N T E N T W I T H ACTIONSCRIPT 3.0
The role of this function is to set column headings, widths, and assign which data field to display. This function starts by declaring the column headings for the DataGrid. It refers to each column using its position in the columns collection. The function then sets the w i d t h and d a t a F i e l d properties for each column. Feel free to modify the w i d t h settings to display more or less of each column. The function finishes by making the first column read-only. The application needs to do this because the users should not be able to change the bookID field. Normally, that field would be the primary key and would be allocated externally. 10. Test the application again. The DataGrid looks much better, as shown in Figure 8-4.
IB
O S
authorBookEditor_deleteme.5wf
fde
View
Control
Debug
Authors and Books Author name
New book
Alison Ambrose
Books
I Delete selected book
ID
Name
Publish
Cost
1
Shopping for profit and pleasure
2002
14.99
4
Fishing tips
1999
23.59
Published y e a r
XML t r e e Friendly Books < pu blish Yea r>200S Alison Ambrose "; authorsXML.authors.author[authors_cbo.selectedIndex]. ^»books.book[elementIndex].replace(fieldName, newXMLString); t r e e j t x t . t e x t = authorsXML.toXMLStringQ;
} This function processes the changes to the cell value in the XML object. It starts by creating a new variable that contains an XML S t r i n g made up of the field name and the new value. The newXMLString variable will contain something in the form of New value . The field name can be taken from one of the three columns and can have only the values of bookName, bookPublishYear, or bookCost. The function then locates the relevant book element, finding which author is selected in the ComboBox and using the row number as the element index. It uses the replace() method to replace the entire existing element with the changed element, passing the S t r i n g value that it created earlier. This function finishes by displaying a string representation of the updated XML object in the TextArea.
CHAPTER 8
17. Test the application again. You should be able to change the values in one of the DataGrid cells and see it immediately update in the TextArea. Notice that each time you edit a cell, the updates take place. That's it for the finished application. If you want to check what you've done, the complete code from the actions layer follows: import f l . d a t a . D a t a P r o v i d e r ; import f l . e v e n t s . D a t a G r i d E v e n t ; var authorsXML:XML; var authorList:XML; var booksList:XML; var request:URLRequest = new URLRequest("authorsAndBooks.xml"); var loader:URLLoader = new URLLoaderQ; initAppQ; stop(); function initApp():void { loader.addEvent Listener(Event.COMPLETE, completeHandler); authors_cbo.addEventListener(Event.CHANGE, changeHandler); addRow_btn.addEventListener(MouseEvent.CLICK, addClickHandler); delete_btn.addEventListener(MouseEvent.CLICK, d e l e t e C l i c k H a n d l e r ) ; books_dg.addEventListener(DataGridEvent.ITEM_EDIT_END, *»itemEditEndHandler); authors_cbo.labelFunction = getFullName; loader.load(request);
} f u n c t i o n completeHandler(e:Event):void { authorsXML = X M L ( e . t a r g e t . d a t a ) ; a u t h o r L i s t = new XML(authorsXML.authors); authors_cbo.dataProvider = new D a t a P r o v i d e r ( a u t h o r L i s t ) ; loadBooks(o); setupDataGrid(); t r e e _ t x t . t e x t = authorsXML.toXMLString();
}
f u n c t i o n itemEditEndHandler(e:DataGridEvent):void { var dg:DataGrid = e . t a r g e t as DataGrid; var f i e l d ¡ S t r i n g = e . d a t a F i e l d ; var row:Number = Number(e.rowlndex); var c o l : i n t = e.columnlndex; var o l d V a l : S t r i n g ; var newVal:String; oldVal = e . i t e m R e n d e r e r , d a t a [ f i e l d ] ; newVal = d g . i t e m E d i t o r I n s t a n c e [ d g . c o l u m n s [ c o l ] . e d i t o r D a t a F i e l d ] ; if (oldVal != newVal) { modifyXMLTree(dg.columns[col].dataField, row, newVal);
}
}
f u n c t i o n changeHandler(e:Event):void { var a u t h o r I n d e x : i n t = e . t a r g e t . s e l e c t e d l n d e x ; loadBooks(authorlndex);
258
}
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
f u n c t i o n addClickHandler(e:MouseEvent):void { var newBookName:String = n a m e j t x t . t e x t ; var newPublishYear:String = y e a r _ t x t . t e x t ; var newBookCost:String = c o s t j t x t . t e x t ; var newBookXML:XML; if(newBookName.length > 0 && newPublishYear.length > 0 && ^•-newBookCost. length > 0) { books_dg.addItem({bookName: newBookName, ^»bookPublishYear: newPublishYear, bookCost: newBookCost}); newBookXML = ; newBookXML.bookName = newBookName; newBookXML.bookPublishYear = newPublishYear; newBookXML.bookCost = newBookCost; authorsXML.authors.author[authors_cbo.selectedlndex]. ^»books.appendChild(newBookXML); t r e e j t x t . t e x t = authorsXML.toXMLStringQ;
}
}
f u n c t i o n deleteClickHandler(e:MouseEvent):void { var bookIndex:int = books_dg.selectedlndex; var a u t h o r l n d e x : i n t = authors_cbo.selectedlndex; i f (booklndex ! = - l ) { books_dg.removeItemAt(bookIndex); delete(authorsXML.authors.author[authorIndex].books. *»book[booklndex]); t r e e j t x t . t e x t = authorsXML.toXMLStringQ;
}
}
function getFullName(item:Object):String{ r e t u r n item.authorFirstName + " " + item.authorLastName;
}
function loadBooks(authorIndex:int):void { var booksDP:DataProvider; booksList = new XML(authorsXML.authors.author[authorIndex].books); booksDP = new DataProvider(booksList); books_dg.dataProvider = booksDP;
}
f u n c t i o n setupDataGridQ:void { books_dg.columns = [ " I D " , "Name", "Publish y e a r " , " C o s t " ] ; books_dg.columns[0].width = 50; books_dg.columns[l].width = 200; books_dg.columns[2].width = 50; books_dg.columns[3].width = 50; books_dg.columns[0].dataField = "bookID"; books_dg.columns[l].dataField = "bookName"; books_dg.columns[2].dataField = "bookPublishYear"; books_dg.columns[3].dataField = "bookCost"; books_dg.columns[0].editable = f a l s e ;
259
CHAPTER 6
f u n c t i o n modifyXMLTree(fieldName:String, e l e m e n t l n d e x : i n t , ^»newVal:String):void{ var newXMLString:String = "" + newVal + * - " < / " + fieldName + " > " ; authorsXML.authors.author[authors_cbo.selectedlndex]. *»books.book[elementIndex].replace(fieldName, newXMLString); t r e e _ t x t . t e x t = authorsXML.toXMLStringQ;
} You can find the completed file saved as authorBookEditor_completed.fla with the chapter resources.
Working in Flex Let's see how you might work through the same example in Flex Builder. In this case, the application will use a class-based approach. 1. Open the starter file AuthorBookEditor.mxml in Flex Builder. You can copy the file and paste it into your existing Flex project. The interface for the application, shown in Design view of Flex Builder, appears in Figure 8-6.
Figure 8-6. The application interface in Design view
As with the Flash version of this example, you'll populate the ComboBox with a list of authors from the external XML document. When an author is selected, the application will show the books for that author in the DataGrid component beneath the author name. It will show a S t r i n g representation of the XML object in the TextArea at the bottom of the interface. To the right, Textlnput controls allow a user to enter new book details.
260
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
2. If you're using a new Flex project, create an assets folder inside the src folder of the project. Copy the authorsAndBooks.xml file to this folder. The file contains the details of the authors and their books. 3. You'll load the XML document with the MyXMLLoaderHelper class that you created in Chapter 5. The class exists in the x m l U t i l i t i e s package, so add a folder of that name to your Flex project. Copy the file MyXMLLoaderHelper.as from the chapter resources to the x m l U t i l i t i e s folder. You can also use the version that you created yourself in Chapter 5. This class file handles the loading of the external XML file. It also returns a specified child element from the XML object. However, you'll need to modify the class file to add a little more functionality for this example. 4. Because the XML file that you're loading is complicated, you're going to define the element from the external file as its own XML object. This approach will allow you to work with just that part of the XML document and make it easier to locate the element for each author. Add two new private variable declarations with the other declarations: p r i v a t e var x m l R o o t S t r i n g : S t r i n g ; p r i v a t e var xmlRoot:XML; The first variable will store the name of the element to use as the new XML object root. In this case, the new XML object xmlRoot will store the element information with all of its child elements. The application will pass in an additional parameter to the loadXML() method to specify the child element to use for the new XML object root. Modify the function as shown in bold here. It will assign the new parameter to the xmlRootString variable. p u b l i c f u n c t i o n loadXML(xmlFile:String, rootElement:String):void { xmlRootString = rootElementj try { xmlLoader.load(new URLRequest(xmlFile));
} catch ( e : E r r o r ) { t r a c e ( " C a n ' t load e x t e r n a l XML document");
}
You also need to modify the completeHandler() private method as shown in bold: p r i v a t e f u n c t i o n completeHandler(e:Event):void { xmlAHContent = XML(e.target.data); xmlRoot = XML (xmlAHContent. child (xmlRootString)); dispatchEvent(new Event(Event.COMPLETE));
} The new line assigns the XML content from the specified child element to the xmlRoot object. Because the c h i l d ( ) method actually returns an XMLList object, the code needs to cast it as an XML object so there isn't a type mismatch.
261
CHAPTER 8
The class file needs a new public method to return the XML object to the calling application. Add the following getXMLQ method: p u b l i c f u n c t i o n getXMLQ: XML { r e t u r n xmlAllContent;
The final change to the class file is to modify the getChildElementsQ public method to find a child of the xmlRoot object instead of the xmlAHContent element. Change the first line as shown here in bold: childElements = xmlRoot.child(elementName); The complete class file follows in case you want to check that you've included all of the changes correctly: package x m l U t i l i t i e s { import flash.net.URLLoader; import flash.net.URLRequest; import m x . c o l l e c t i o n s . X M L L i s t C o l l e c t i o n ; [Bindable] p u b l i c class MyXMLLoaderHelper { p r i v a t e var xmlAllContent:XML; p r i v a t e var xmlLoader¡URLLoader; p r i v a t e var x m l R o o t S t r i n g : S t r i n g ; p r i v a t e var xmlRoot:XML; p r i v a t e var childElements:XMLList; p r i v a t e var c h i l d E l e m e n t s C o l l e c t i o n : XMLListCollection; p u b l i c f u n c t i o n MyXMLLoaderHelperQ { xmlLoader = new URLLoaderQ; xmlLoader.addEvent Listener(Event.COMPLETE, completeHandler);
} p u b l i c f u n c t i o n loadXML(xmlFile:String, ^»rootElement:String):void { xmlRootString = rootElement; try { xmlLoader.load(new URLRequest(xmlFile));
}
catch ( e : E r r o r ) { t r a c e ( " C a n ' t load e x t e r n a l XML document");
}
}
p u b l i c f u n c t i o n getXMLQ:XML { r e t u r n xmlAHContent;
}
p u b l i c f u n c t i o n getChildElements(elementName:String): «-XMLListCollection { childElements = xmlRoot.child(elementName); childElementsCollection = new XMLListCollection(childElements); return childElementsCollection;
} 262
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
p r i v a t e f u n c t i o n completeHandler(e:Event):void { xmlAHContent = XML (e. t a r g e t , d a t a ) ; xmlRoot = XML (xmlAHContent . c h i l d ( x m l R o o t S t r i n g ) ) ; dispatchEvent(new Event(Event.COMPLETE));
}
}
You'll make further changes to this class as part of building this example, so leave the file open in Flex Builder. 5. You need to initialize the application and call the loadXML() method of our custom class to load the external document into the application. Switch to the AuthorBookEditor.mxml file and add the following code, including the i n i t A p p Q function, to an block at the top of the file: import m x . c o l l e c t i o n s . X M L L i s t C o l l e c t i o n ; import xmlUtilities.MyXMLLoaderHelper; import mx.events.FlexEvent; p r i v a t e var myXMLLoader:MyXMLLoaderHelper; private function initApp(e:FlexEvent):void { myXMLLoader = new MyXMLLoaderHelperQ; myXMLLoader.addEvent Listener(Event.COMPLETE, completeHandler); myXMLLoader.loadXML("assets/authorsAndBooks.xml", " a u t h o r s " ) ;
}
p r i v a t e f u n c t i o n completeHandler(e:Event):void { } This code starts by importing the classes that the application needs, including the XMLListCollection, the custom class MyXMLLoaderHelper, and the FlexEvent class. The XMLListCollection wrapper class will work with XMLListCollection objects returned from the custom class. As you saw earlier in the book, this class provides additional functionality to an XMLList object and is suitable as a DataProvider for list-based components. The code block then declares a private variable, myXMLLoader, which is an instance of the custom class. The code block includes the i n i t A p p Q function, which creates a new instance of the MyXMLLoaderHelper class and assigns an event listener to respond to the complete event. The function also calls the loadXML() method of the custom class, passing the external XML document file name and target element name. I've included the function signature for the completeHandlerQ function without any code. You'll need to call this function in the creationComplete attribute of the element as shown in bold in the following code, so make the change in your own file. 6. You'll modify the completeHandlerQ function to display the author names in the ComboBox control. The code will call the getChildElementsQ method of the custom class to return an XMLListCollection containing all of the elements. It will assign this object as the dataProvider property for the ComboBox.
263
The completeHandlerQ function will also populate the TextArea with a S t r i n g representation of the loaded XML object. The application will use this TextArea control to keep track of the current contents of the XML object. Add the following lines to the completeHandler() function: a u t h o r s _ c b o . d a t a P r o v i d e r = myXMLLoader.getChildElements("author"); t r e e j t x t . t e x t = myXMLLoader .getXMLQ . t o X M L S t r i n g Q ; The first line sets the dataProvider property of the ComboBox to the X M L L i s t C o l l e c t i o n returned by the g e t C h i l d E l e m e n t s ( ) method of the myXMLLoader class. As the full name doesn't appear in a single element in the dataProvider, the application will need to use a l a b e l F u n c t i o n in the ComboBox control to display this value. You've seen this function earlier in the book. Add it to the block. private function getFullName(item:Object)¡String { r e t u r n item.authorFirstName + " " + item.authorLastName;
The function joins the author's first and last names with a space in between to create a full name. You also need to assign the getFullName() function to the ComboBox so it knows how to create the label. Modify the i n i t A p p ( ) function as shown here in bold to assign it as the l a b e l F u n c t i o n property. private function initApp(e:FlexEvent):void { myXMLLoader = new MyXMLLoaderHelperQ; myXMLLoader.addEventListener(Event.COMPLETE, completeHandler); authors_cbo.labelFunction = getFullNamej myXMLLoader,loadXML("assets/authorsAndBooks.xml", " a u t h o r s " ) ;
} Test the movie and check that the ComboBox populates correctly with the full name of each author. Figure 8-7 shows how the interface should appear if you run the application and view it in a web browser. Authors and Books
PnhlKhpri ypar
pu blishcr>Friend ly Books^/pu blishcr^ 2000 c/pUblishYear> ^-authors-* •«"author authorID="l""s•^autfiorFiratNamc ^Alison-^/a uth orFirstName > Anibiuae-i/dutliuiLd^lNdiIn «-books-» «"book bookID=" Shopping for profit and
Figure 8-7. The ComboBox populates from the external XML document.
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
8. Now it's time to load the books for the author selected in the ComboBox. When the application first loads, it should show the books for the first author. The class file needs a public method that returns the element for the selected author. Switch to the custom class file and add the following getBooks() public method: public function getBooks(authorIndex:int):XMLListCollection { bookElements = xmlAHContent. authors. a u t h o r [ a u t h o r l n d e x ] . books. book; bookElementsCollection = new XMLListCollection(bookElements); r e t u r n bookElementsCollection;
} This method takes as its only argument the selected author index from the ComboBox. This value equates to the index of the element in the XML object. The method creates an XMLList called bookElements from the list of all elements for the current author. It does this using the E4X expression xmlAHContent.authors, author [author Index] .books, book. The get Books () method then creates an XML L i s t C o l l e c t ion from this XMLList object and returns it to be used as the dataProvider property for the DataGrid. 9. The application file calls this public method in two places. First, it calls the method after the ComboBox is loaded so the application can display the first author's books. It also calls the method in response to a change in the selectedlndex of the ComboBox so that the application can repopulate the DataGrid with the correct books. Switch to the application file and add the following line to the completeHandler() function: books_dg.dataProvider = myXMLLoader.getBooks(o); This line calls the getBooks() public method when the application initializes, passing the first author index of 0. It sets the returned XMLListCollection as the dataProvider for the DataGrid, showing the books for the first author whose name displays in the ComboBox. For the second method call, add the following line to the i n i t A p p ( ) function: authors_cbo.addEventListener(ListEvent.CHANGE,
changeHandler);
Whenever the author chosen in the ComboBox changes, the application will call the changeHandler() function. Now, you need to add the changeHandler() function. Add the following code block to your application file: p r i v a t e f u n c t i o n changeHandler(e:Event):void { books_dg.dataProvider = myXMLLoader,getBooks(e.target, ^»selectedlndex);
} The changeHandler() function again sets the dataProvider property for the DataGrid by calling the getBooks() public method of the myXMLLoader object. This time, it passes the selectedlndex from the ComboBox to specify which element to target. It finds the correct author using e . t a r g e t . s e l e c t e d l n d e x . 10. You need to set up the DataGrid to display the columns correctly before testing the application. If you tested the application now, you would see only the bookID displayed in the DataGrid; the remaining information wouldn't appear.
265
CHAPTER 6
The easiest way to modify the setup of the DataGrid is within the MXML element itself. Modify the element as shown here. Notice that you need to rewrite the element with a closing tag. The element includes the list of all columns to display inside the element. Each column contains a headerText, dataField, and width attribute. The dataField determines which element to display from the dataProvider, and the value equates to the name of a child element. Notice that the expression uses an @> sign to indicate that bookID is an attribute. The first element also sets the first column to be read-only so that the user can't edit the bookID value. Normally, this value would equate to the primary key in the data source and would be maintained externally. Because the element contains the attribute e d i t a b l e = " t r u e " , it will be possible for a user to edit all other columns. 11. Test the application again. You should see the book information displaying correctly in the DataGrid, as shown in Figure 8-8. Authors and Books Author name
1 | Douglas Donaldson
Books
m
New book Name
Delete selected book
Published y e a r
ID
Name
Publish
Cost
5
O u t s t a n d i n g dinner parties
2003
35.99
9
C o o k i n g for fun a n d profit
2007
24.75
Cost
Add book
XML tree < pu bl ish er> Fri e n d ly Bo o k s < pu bl ish Y e a r> 20 08 < a uth o rFi rstN a m e > Al ¡so n < a uth o rLastN a m e > Am b rose < b o o k N a r n e > S h o p p i n g for profit a n d
F
F i g u r e 8 - 8 . The DataGrid synchronizes with the selected author.
266
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
Now that the interface is set up, it's time to look at adding, editing, and deleting book information. Remember that the application will change only the XML object in the SWF application, and not in the external document, as Flex isn't capable of updating external content. When you finish building this application, the class file will contain public methods that add, edit, and delete elements from the XML object. The application file will call these methods to process the updates made in the interface. Before it continues, it will need to know that the updates have been completed. Each of the public methods must dispatch an event to the application, notifying it that this has occurred. After the application receives this event, it can refresh the dataProvider for the DataGrid and display the updated XML object in the TextArea control. This process must occur with every type of modification made in the interface. 12. You'll create a new event in the class file to be dispatched when changes have been completed. This event is called xmlUpdated, and you'll create a handler function in the application file to respond when it is notified of the event. Switch to the custom class file MyXMLLoaderHelper.as. Declare the event by adding the following [Event] metadata tag above the class declaration: [Event(name="xmlUpdated",
type="flash.events.Event")]
This is an event called xmlUpdated with an Event type. In this case, the application doesn't need a custom Event object, because you're not passing information with the event. The metadata tag needs to declare this event so that the application file will recognize it correctly. 13. Switch to the application file and add a handler for this new event in the i n i t A p p ( ) function, as shown here: myXMLLoader.addEventListener("xmlUpdated",
xmlUpdatedHandler);
Whenever the application file is notified of the xmlUpdated event, it will call the xmlUpdatedHandler() function. Add the xmlUpdatedHandler() function that follows to the block: p r i v a t e f u n c t i o n xmlUpdatedHandler(e:Event):void { books_dg.dataProvider = myXMLLoader,getBooks( *»authors_cbo.selectedIndex); t r e e j t x t . t e x t = myXMLLoader .getXMLQ .toXMLStringQ;
} This handler function sets the dataProvider property for the books_dg control, which effectively refreshes the content in the DataGrid. It also displays a S t r i n g representation of the XML object in the TextArea component so that you can see the updated content. You'll want to repeat these steps every time a user changes book details. 14. Now it's time to add more functionality so that a user can make modifications to the books. You'll start by seeing how to add a new book to the currently selected author. You need an event handler that responds when a user clicks the Add book button. Add the following line to the i n i t A p p ( ) function: addRow_btn.addEventListener(MouseEvent.CLICK,
addClickHandler);
267
CHAPTER 6
You also need to add the addClickHandler() function to the block. The function follows. Note that it references a public method of a custom class, addBookQ, that you have yet to create. p r i v a t e f u n c t i o n addClickHandler(e:MouseEvent):void { var newBookName:String = name_txt.text; var newPublishYear:String = y e a r _ t x t . t e x t ; var newBookCost:String = c o s t _ t x t . t e x t ; var a u t h o r I n d e x : i n t = authors_cbo.selectedlndex; if(newBookName.length > 0 && newPublishYear.length > 0 *»&& newBookCost.length > 0) { myXMLLoader.addBook(authorIndex, newBookName, newPublishYear, ^»newBookCost);
}
}
The addClickHandler() function receives a MouseEvent as an argument. The function declares variables for each of the user entries and checks that the user entered a value for each one. If this is the case, the function calls the addBookQ method of the myXMLLoader instance, passing the selected author index as well as the new values. You'll set up this public method next. I didn't add any functionality to deal with the situation where a user doesn't enter all of the information for a book and then clicks the button. Feel free to extend this example to display an error message in the interface when this occurs. 15. Switch to the MyXMLLoaderHelper class file. Add the following addBookQ public method to process the new book details: p u b l i c f u n c t i o n addBook(authorIndex:int, bookName:String, *»bookPublishYear:String, b o o k C o s t : S t r i n g ) : v o i d { var newBookXML:XML; newBookXML = ; newBookXML.bookName = bookName; newBookXML.bookPublishYear = bookPublishYear; newBookXML.bookCost = bookCost; xmlAHContent .authors. author [ author I n d e x ] . books. ^»appendChild(newBookXML); dispatchEvent(new Event("xmlUpdated"));
} This method receives the selectedlndex from the ComboBox and the new book values as parameters. The selectedlndex is called authorlndex, and this value corresponds with the node index for the element in the XML object. The addBookQ method declares a new local XML object called newBookXML and populates it with a element containing an empty bookID attribute. It then adds the bookName, bookPublishYear, and bookCost properties to this object. Notice that, instead of using appendChildQ, you've used dot notation to speed up the process. This method finishes by using the appendChildQ method to add the newly created element as the last child of the current author's element. In the last line, the method finishes by dispatching an xmlUpdated event to inform the application that the updating of the XML object is complete. The application will then respond by calling the xmlUpdatedHandlerQ function.
268
M O D I F Y I N G X M L C O N T E N T W I T H ACTIONSCRIPT 3.0
16. Test the application and enter a new book. The application should add the new book to the selected author, updating both the DataGrid and TextArea. Figure 8-9 shows a new book added to the author Douglas Donaldson. It appears in both the XML tree in the TextArea and as a new row at the bottom of the DataGrid.
Figure 8-9. Adding a new book
17. You'll now add functionality so that a user can delete a book from the DataGrid and XML object. By selecting a row and clicking the Delete selected book button, a user can remove a book. Start by adding an event listener to respond when the Delete selected book button is clicked. Add the following line to the i n i t A p p ( ) function in the MXML file: delete_btn.addEventListener(MouseEvent.CLICK,
deleteClickHandler);
Add the following deleteClickHandler() function to the block. This method calls a deleteBookQ public method from the class file that you'll add shortly. p r i v a t e f u n c t i o n deleteClickHandler(e:MouseEvent):void { var bookIndex:int = books_dg.selectedlndex; var a u t h o r l n d e x : i n t = authors_cbo.selectedlndex;; i f (books_dg.selectedlndex ! = - l ) { myXMLLoader.deleteBook(authorIndex, booklndex);
}
}
The deleteClickHandler() function receives a MouseEvent as an argument and declares two variables: one for the selected DataGrid row index, called booklndex, and one for the selected author index, called authorlndex. It assigns values for these variables. The function then checks that the user has selected a row in the DataGrid. If no row is selected, the selectedlndex will have a value of - l .
269
CHAPTER 6
The function finishes by calling the deleteBook() public method of the myXMLLoader object to carry out the deletion. It passes both the authorlndex and booklndex so that the public method will be able to locate the correct element. 18. Switch to the MyXMLLoaderHelper class file. Add the following deleteBookQ public method to handle the book deletion: public function deleteBook(authorIndex:int, bookIndex:int):void { delete (xmlAHContent. authors, author [author I n d e x ] . books. *»book[booklndex]); dispatchEvent(new Event("xmlUpdated"));
} This public method uses the ActionScript 3.0 d e l e t e ( ) operator to remove the relevant element. It locates the correct element with the E4X expression xmlAHContent.authors. author[authorIndex] .books.book[bookIndex]. The method then dispatches the xmlUpdated event to notify the application that the updates are finished. 19. Test the application and make sure that you can remove a row from the DataGrid. If you look at the TextArea control, you should see that the element has been removed from the XML object as well. 20. The final task is to allow the user to edit entries in the DataGrid. A user will be able to modify any value except for the booklD. As I've noted, that value is usually allocated by the external data source. As with the Flash example, the application will handle when the editing of an individual cell ends by responding to a DataGridEvent. The updating will happen immediately after each cell has been edited. Switch to the application file. Import the DataGridEvent class with the following import statement. Add it with the other import statements at the top of the block. import mx.events.DataGridEvent; Add the following event listener to the i n i t A p p Q function: books_dg.addEventListener(DataGridEvent.ITEM_EDIT_ENDj ^•itemEditEndHandler); The application responds when the user finishes editing a cell by referencing ITEM_EDIT_END. This value indicates that the editing of a cell is ending, and it will allow the application to capture both the starting and ending values. Add the following itemEditEndHandler() function to process the changes to the cell value:
270
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
f u n c t i o n itemEditEndHandler(e:DataGridEvent):void { var a u t h o r l n d e x : i n t = authors_cbo.selectedIndex; var dg:DataGrid = e . t a r g e t as DataGrid; var f i e l d : S t r i n g = e.dataField; var row:Number = Number(e.rowlndex); var c o l : i n t = e.columnlndex; var o l d V a l : S t r i n g = e . i t e m R e n d e r e r . d a t a [ f i e l d ] ; var newVal:String = dg.itemEditorInstance[dg.columns[col]. ^»editorDataField]; if (oldVal != newVal) { myXMLLoader.modifyXMLTree(authorlndex, *»dg.columns[col].dataField, row, newVal);
}
}
This function calls a public method, modifyXMLTree(), which you have yet to add to the class file. It starts by declaring an object for the DataGrid and locating it with the expression e . t a r g e t . The code uses as to type the object correctly as a DataGrid. The function identifies the dataField being modified as well as the row and column being changed. It also declares variables for the original and changed values so that they can be compared to see if a change actually took place. If the values are not the same—that is, something has been modified—theitemEditEndHandler() function calls the modifyXMLTreeQ public method of the myXMLLoader object. It passes the name of the field being edited; the row number in the DataGrid, which will equate to the node index; and the new value entered by the user. 21. Switch to the class file and add the modifyXMLTree() method shown here: public f u n c t i o n modifyXMLTree(authorIndex:int, fieldName:String, ^»elementlndex:int, newVal:String):void{ var newXMLString:String = "" + newVal + * - " < / " + fieldName + ">"; xmlAHContent. authors, author [author Index] .books. book[ element Index]. ^»replace(fieldName, newXMLString); dispatchEvent(new Event("xmlUpdated"));
} This public method creates a new S t r i n g variable that contains the changed value, formatted as an XML element. It uses the fieldName value passed from the MXML file for the element name. It will produce a variable containing the structure New value. The modifyXMLTree() method then uses an E4X expression to locate the relevant element being edited. In the expression xmlAHContent.authors.author[authorIndex]. books. book[elementIndex], the authorlndex indicates which element to target; the elementlndex indicates the index to use. The function uses the replaceQ method to update the existing element and replace it with the provide S t r i n g content. Finally, the function dispatches the xmlUpdated event so that the application can refresh the interface. 22. Test the application and check that you can edit the name, publish year, and cost of each item. Also make sure that the contents of the TextArea control are updated with each of your changes.
271
CHAPTER 6
Congratulations! You've now completed the Flex application. The complete MyXMLLoaderHelper.as class file follows so you can check your work. The file is also included with the chapter resources. package x m l l l t i l i t i e s { import f l a s h . e v e n t s . E v e n t ; import flash.net.URLLoader; import flash.net.URLRequest; import m x . c o l l e c t i o n s . X M L L i s t C o l l e c t i o n ; [Bindable] [Event(name="xmlUpdated", t y p e = " f l a s h . e v e n t s . E v e n t " ) ] p u b l i c class MyXMLLoaderHelper { p r i v a t e var xmlAllContent:XML; p r i v a t e var xmlLoader¡URLLoader; p r i v a t e var x m l R o o t S t r i n g : S t r i n g ; p r i v a t e var xmlRoot:XML; p r i v a t e var childElements:XMLList; p r i v a t e var c h i l d E l e m e n t s C o l l e c t i o n : XMLListCollection; p r i v a t e var bookElements:XMLList; p r i v a t e var bookElementsCollection:XMLListCollection; p u b l i c f u n c t i o n MyXMLLoaderHelperQ { xmlLoader = new URLLoaderQ; xmlLoader.addEvent Listener(Event.COMPLETE, completeHandler);
} p u b l i c f u n c t i o n loadXML(xmlFile:String, r o o t E l e m e n t : S t r i n g ) : v o i d { xmlRootString = rootElement; try { xmlLoader.load(new URLRequest(xmlFile));
}
catch ( e : E r r o r ) { t r a c e ( " C a n ' t load e x t e r n a l XML document");
}
}
p u b l i c f u n c t i o n getXML():XML { r e t u r n xmlAHContent;
}
p u b l i c f u n c t i o n getChildElements(elementName:String): «-XMLListCollection { childElements = xmlRoot.child(elementName); childElementsCollection = new XMLListCollection(childElements); return childElementsCollection;
}
p u b l i c f u n c t i o n getBooks(authorIndex:int)¡XMLListCollection { bookElements = x m l A H C o n t e n t . a u t h o r s . a u t h o r [ a u t h o r I n d e x ] . «•books, book; bookElementsCollection = new XMLListCollection(bookElements); r e t u r n bookElementsCollection;
}
272
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
p u b l i c f u n c t i o n addBook(authorIndex:int, bookName:String, «bookPublishYear:String, bookCost:String):void { var newBookXML:XML; newBookXML = ; newBookXML.bookName = bookName; newBookXML.bookPublishYear = bookPublishYear; newBookXML.bookCost = bookCost; xmlAHContent .authors. author [ author I n d e x ] . books. «appendChild(newBookXML); dispatchEvent(new Event("xmlUpdated"));
} public function deleteBook(authorIndex:int, booklndex:int):void { delete(xmlAHContent. authors. a u t h o r [ a u t h o r l n d e x ] . «•books. book[ booklndex]); dispatchEvent(new Event("xmlUpdated"));
}
}
p u b l i c f u n c t i o n modifyXMLTree(authorIndex:int, f i e l d N a m e : S t r i n g , «elementlndex: i n t , newVal:String) : v o i d { var newXMLString:String = "" + newVal + « " < / " + fieldName + " > " ; xmlAHContent . a u t h o r s . a u t h o r [ a u t h o r l n d e x ] . b o o k s . «book[elementIndex],replace(fieldName, newXMLString); dispatchEvent(new Event("xmlUpdated")); p r i v a t e f u n c t i o n completeHandler(e:Event):void { xmlAHContent = XML(e.target.data); xmlRoot = XML (xmlAHContent . c h i l d ( x m l R o o t S t r i n g ) ) ; dispatchEvent(new Event(Event.COMPLETE));
}
}
}
The complete application file follows. It is also saved as AuthorBookEditor_completed.mxml with the chapter resources. 0 && newPublishYear.length > 0 newBookCost.length > 0) { myXMLLoader,addBook(authorIndex, newBookName, ^»newPublishYear, newBookCost);
}
}
p r i v a t e f u n c t i o n deleteClickHandler(e:MouseEvent):void { var bookIndex:int = books_dg.selectedlndex; var a u t h o r l n d e x : i n t = authors_cbo.selectedlndex;; i f (books_dg.selectedlndex ! = - l ) { myXMLLoader.deleteBook(authorIndex, booklndex);
}
274
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
p r i v a t e f u n c t i o n itemEditEndHandler(e:DataGridEvent):void { var a u t h o r I n d e x : i n t = authors_cbo.selectedlndex; var dg:DataGrid = e . t a r g e t as DataGrid; var f i e l d ¡ S t r i n g = e . d a t a F i e l d ; var r o w : i n t = e.rowlndex; var c o l : i n t = e.columnlndex; var o l d V a l : S t r i n g = e . i t e m R e n d e r e r . d a t a [ f i e l d ] ; var newVal:String = d g . i t e m E d i t o r I n s t a n c e [ d g . c o l u m n s [ c o l ] . «editorDataField]; if (oldVal != newVal) { myXMLLoader,modifyXMLTree(authorIndex, « d a t a F i e l d j row, newVal)
}
dg.columns[col].
}
p r i v a t e f u n c t i o n xmlUpdatedHandler(e:Event):void { books_dg.dataProvider = myXMLLoader,getBooks(authors_cbo. «selectedlndex); t r e e _ t x t . t e x t = myXMLLoader.getXMLQ.toXMLStringQ;
} ]]>
275
CHAPTER 6
Before we finish, there are some points to consider from these examples.
Points to note about the example There are several points to note about these exercises: • The example changes only the structure and content of the XML object within the SWF application. It doesn't update the external file. Neither Flash nor Flex has the ability to update external content for obvious security reasons. You would need some server-side code to carry out the updates. You'll see how to communicate with the server in the next chapter. •
In both versions of the application, if a user clicked a button inappropriately, we didn't display an error message. As an exercise, you might want to add this functionality to the application.
• There is no validation of the new or changed entries made by the user. In a real-world application, you would handle the user entries a little more robustly. For example, you would check that the new value entered in the year field was an appropriate number. You would also check that the price was a numeric value, and format it to display as a S t r i n g with two decimal places. I'll leave those niceties up to you as an area for self-study. • There are other ways that you could have created the Flex application. Flex provides data binding opportunities that may have made it easier to keep the XML object synchronized with a DataGrid dataProvider and a TextArea control. However, the aim of this exercise was to show you how to modify XML content within Flex, so that's what we did! Feel free to tackle the job of modifying the example yourself if you wish.
276
MODIFYING X M L CONTENT W I T H ACTIONSCRIPT 3.0
Summary In this chapter, I've shown you how to update the contents of an XML object. You've seen how to add, edit, and delete XML content, as well as how to modify element names and namespaces. We worked through an example that demonstrated how you might modify an XML tree with ActionScript 3.0. You saw how to do so in Flash and Flex, using both a procedural and class-based approach. While it was possible to update the content within the SWF application, your updates didn't affect the external content. You would need the SWF application to communicate with the server where the updates would be processed. Communicating with the server is the topic of the next chapter.
277
Chapter 9
COMMUNICATING WITH THE SERVER So far in this book, you've seen how to work with XML content in both Flash and Flex. We started by examining the new XML class. We've loaded external static XML documents and learned how to use a SWF application to make modifications to an XML object. In this chapter, we'll focus on the interaction between the SWF and server-side files. The server-side logic is provided by an application server and a server-side language like PHP, VB .NET, or ColdFusion. The SWF application needs to request a page written in one of these languages and send variables for processing. The variables may be sent so that the server-side page can update the data source. They may also be sent so that the server-side page can do some processing and send a response. The examples in this chapter won't use Flash or Flex to update an external source of data. Instead, you'll see how to send variables that are processed by a server-side page using the URLLoader class in Flash and Flex applications. You'll also see how a SWF application can receive a response and incorporate that in the Flash or Flex application. Additionally, we'll cover how to use the tag and HTTPService class in Flex applications to send variables to a server-side page and incorporate the server response in the application. We'll work through several examples to demonstrate how you can achieve these aims. These examples use VB .NET, but I've provided alternative copies of the file written in PHP and ColdFusion, if you prefer working in those languages. As always, you can find the examples for this chapter at http://www.-Friendsofed. com. You'll need to work with an application server to complete the examples. This could be the .NET Framework, or an Apache or ColdFusion server.
279
CHAPTER 6
You can run the examples on your own computer or copy the finished code to a public web server. I won't go through how to set up an application server on your local machine, so you'll need to work through that process yourself. Don't forget that loading external data in still governed by the Flash Player security rules. Basically, you can't load data from a different domain without some type of intervention. If you want to find out more about these rules, see the section about Flash Player 10 security in Chapter 5. Let's get started with an overview of the process. This way, you will understand what's really happening when you exchange data between a web browser and application server.
Sending data to the server For security reasons, you can't use Flex to carry out server-based tasks such as sending e-mail, deleting or modifying files, or updating databases. However, both a Flash and Flex application can send variables to an application server so that it can carry out the processing on behalf of the SWF application. A SWF application might send variables to filter the results of a database query or have the variables processed by the server-side file in some way. Processing could include generating an e-mail from the values entered into the SWF file or saving them to a text file. The SWF application may also be sending values that need to be updated in a database or a static XML document. The URLLoader class provides a mechanism for sending variables when the application requests a server-side page. This class is available to all SWF applications, whether they are created in Flash or Flex. The tag and HTTPService class achieve the same result and are both available to SWF applications created in Flex. In this chapter, you'll see how to use all of these approaches to send content from a SWF file to be processed by a server-side file. Before we see the ActionScript code that you'll need to use, we need to cover the following areas: •
How to structure the path to the server-side file
•
How to choose an HTTP method to use to send the variables
•
How to choose a content type for the variables being sent
We'll cover each of these areas in turn, starting with creating the path to the server-side file.
Structuring the file path One of the most important points to remember is that when you specify the URL for the element, HTTPService class, or URLLoader class, you must make sure that you include the full server path. Instead of writing just the file name, you need to include the address of the web server, starting with the h t t p : / / portion, as shown here: http://localhost/FlexApp/login.apx This path is necessary so that the application server knows that it is dealing with a file containing server-side code. Without the full path, the web server won't know to process server-side code in the page, and will treat it as a text file.
280
C O M M U N I C A T I N G W I T H THE SERVER
In the case of a URLLoader object, you might structure the code to work with the full path as follows: var serverPage:String = " h t t p : / / l o c a l h o s t / F l e x A p p / l o g i n . a p x " ; var request:URLRequest = new URLRequest(serverPage); This example references the application server using h t t p : / / l o c a l h o s t . This path is very common when working with an application server installed on a local desktop computer. The FlexApp part of the path in the code sample usually corresponds to the name of the folder containing your application files. In this example, the application files live in a folder called FlexApp on the local machine. When you move the application to its production environment, the URL will usually change to the domain name associated with the site. You don't usually include the folder name for the files on the web server. A production URL might look like the following: http://www.foe.com/login.apx The code used by the URLLoader object in this case might look like this: var serverPage:String = " h t t p : / / w w w . f o e . c o m / l o g i n . a p x " ; var request:URLRequest = new URLRequest(serverPage); Because you need to switch the path when moving to a development environment, it's good practice to set a variable that specifies this path. You can then refer to this variable throughout your application. You can see how you might take this approach with the URLLoader class in the following code block: var s e r v e r P a t h : S t r i n g = " h t t p : / / l o c a l h o s t / F l e x A p p / " ; var serverPage:String = serverPath + " l o g i n . a p x " ; var request:URLRequest = new URLRequest(serverPage); When you need to move the site to its production environment, you can change the value of the serverPath variable to update the application. var s e r v e r P a t h ¡ S t r i n g = " h t t p : / / w w w . f r i e n d s o f e d . c o m / " ; var serverPage:String = serverPath + " l o g i n . a p x " ; var request:URLRequest = new URLRequest(serverPage); Using this approach makes it much easier to manage the location of the server-side files in your application. When you set a variable for the server path, it means that you need to make a change in only one place to update the entire application. Next, let's see how you can actually send the variables from a SWF application to a server-side file.
Sending the variables You have choices about both the method and format to use when sending variables from a SWF application to an application server. The method refers to the HTTP method; the format refers to how the variables are structured.
281
CHAPTER 6
Choosing a method You send variables from one web page to another using the Hypertext Transfer Protocol (HTTP). This is the standard way to transfer variables from a web page to a web or application server. You'll notice that the address of all web pages starts with the letters h t t p : / / , indicating that they use this protocol for communication. HTTP provides for eight different ways to communicate: HEAD, GET, POST, PUT, DELETE, TRACE, OPTIONS, and CONNECT. GET and POST are the most common methods, and the URLLoader class allows you to work with both. The HTTPService class and element allow you to work with the POST, HEAD, OPTIONS, PUT, TRACE, and DELETE methods. We'll focus on GET and POST here, reviewing how they work and the difference between the two. The GET method requests a resource from the web server. It is intended only for retrieving information from the web server. In itself, it doesn't cause changes to the web server. It's possible to send variables with the GET method when requesting data from the server, but any variables that you do send will be available in clear text to anyone viewing the page URL. The GET method isn't suitable for sending large amounts of data. The POST method submits data that will be processed on the server. The variables that you send are included when you make the request. The POST method is usually used when you submit a form containing data to the server. So, which method should you use when working with SWF applications? You should use the GET method when you want to send variables that filter the data returned by the application server. You should use the POST method when you are sending variables that will update the content through the application server. You don't need to explicitly set the GET method for the URLLoader, element or HTTPService class, as it is the default value. You need to add the method property to your application only if you want to use the POST method. The following code block shows how to set the method property when working with a URLLoader object: var s e r v e r P a t h : S t r i n g = "http://www.-Friendsofed.com/"; var serverPage:String = serverPath + "getCountries.apx"; var request:URLRequest = new URLRequest(serverPage); request.method = URLRequestMethod.POSTj You can set the method attribute of an element to achieve the same effect, as shown here: You can set the method in the HTTPService class using ActionScript, as shown in the next lines: xmlService = new HTTPServiceQ; xmlService.method = "POST";
282
C O M M U N I C A T I N G W I T H THE SERVER
Choosing the format You may also need to specify the format for the variables using the contentType property. This format indicates how the variables should be encoded before they're sent to the server. The contentType property equates to the MIME type on the web server. The default value for SWF applications is application/x-www-form-urlencoded. When applications use this setting, the variables are sent as name/value pairs, just as you would see in an HTML form. The name of each variable is separated from the value with an equal sign (=). There is no limit to the number of variables that a SWF application can send. Each of the name/value pairs is separated from the others by the ampersand character (&). In addition, the names and values are encoded before they are sent. This type of encoding is slightly different from the encoding that you'll sometimes see in a web address. Spaces are encoded as a plus (+) character. Other reserved characters—such as a question mark (?), colon (:), and equal sign (=)— use their own encoding. Nonalphanumeric characters are replaced by a percent sign (%), followed by two hexadecimal digits that represent the ASCII code of the character. If the name/value variable pairs are sent to the web server in an HTTP GET request, the values are added to the address of the requested URL. They appear in the section starting with the question mark (?) after the page name, as shown in this example: http://www.foe.com?varl=valuel&var2=value2&var3=value3 So, if a web page sends values using the GET method, the name/value pairs are clearly visible to a user in the address bar of a web browser. When a SWF application sends variables using this method, the server-side page requested doesn't actually load into the browser window, so the name/value variable pairs are never visible. If the application uses the POST method, name/value variable pairs appear in the body of the request, after the HTTP headers. They aren't included in the URL, so they aren't visible to the user. It's also possible to send more data with the POST method than with the GET method. In addition to the default contentType setting in a SWF application, you can also use the a p p l i c a t i o n / x m l setting. You would use this setting to send the variables as an XML document. The a p p l i c a t i o n / x m l setting is often appropriate if you need to send raw XML data to the serverside page for processing. When using the URLLoader class, you would change the contentType for the request with the following code: var s e r v e r P a t h : S t r i n g = "http://www.-Friendsofed.com/"; var serverPage:String = serverPath + "getCountries.apx"; var request:URLRequest = new URLRequest(serverPage); request.contentType = "application/xml"; You would do the following with the element:
283
CHAPTER 6
The approach with the HTTPService is similar: xmlService = new HTTPServiceQ; xmlService.contentType = "application/xml"j Now that you understand some of the considerations for sending data to the server, let's see how to send variables with the URLLoader class.
Working with the URLLoader class Both Flash and Flex can work with the URLLoader class. As you learned in Chapter 5, the URLLoader class is one of the classes in the f l a s h . n e t package, along with the URLRequest and URLVariables classes. These classes work together to provide the functionality for requesting external content from a static or server-side page. The URLLoader class requests a URL from the server and handles the response. You use it to load information from external files. As well as well-formed XML documents, the URLLoader class can also load raw text files and text files that contain name/value variable pairs. We'll focus on dynamically generated XML documents here. These documents exist only as a stream of information sent by a server-side page. Of particular interest to us in this chapter is the URLLoader class's ability to send variables with a request.
Sending variables with the URLLoader class The URLLoader class sends variables to a server-side page using a URLVariables object. You specify the name of the variable with dot notation, in the same way as you would specify a property of an object. The following code block shows a URLVariables object named params. This example shows how to send username and password variables to the server using the HTTP POST method: var s e r v e r P a t h : S t r i n g = " h t t p : / / l o c a l h o s t / F l e x A p p / " ; var serverPage:String = serverPath + " l o g i n . a p x " ; var request:URLRequest = new URLRequest(serverPage); var params:URLVariables = new URLVariablesQ; params.username = "sas"; params.password = " s e c r e t " ; request.data = params; request.method = URLRequestMethod.POST; loader.load(request); /
\
As I mentioned in Chapter 5, make sure you encode any & characters in the variable values with %26. Because the SWF application uses this character as a delimiter for variable pairs, you'll get unintended variable names and values if the character is not encoded.
284
C O M M U N I C A T I N G W I T H THE SERVER
Receiving a response When a SWF application sends variables to a server-side page, it needs to know that the response has been processed successfully. The server will usually send back a return value. In the case where the variables provide the filter, this will be the requested data. In other cases, it might be a notification of the success or failure of the processing request. If you're working with the URLLoader class, a successful request that receives a response dispatches the complete event. In order to work with the response, you need to add an event handler for this event, as shown in the following code block. The relevant line appears in bold, and you can see it with the other code that you would need to make the request. var s e r v e r P a t h : S t r i n g = " h t t p : / / l o c a l h o s t / F l e x A p p / " ; var serverPage:String = serverPath + " l o g i n . a p x " ; var request:URLRequest = new URLRequest(serverPage); var loader:URLLoader = new URLLoaderQ; var params:URLVariables = new URLVariablesQ; loader.addEventListener(Event.COMPLETE, completeHandler)j params.username = "sas"; params.password = " s e c r e t " ; request.data = params; request.method = URLRequestMethod.POST; loader.load(request); When the complete event is dispatched, the completeHandler() function deals with the processing of the reply from the server. >
\
You don't need to name the event handler function with the event name, as I have done here. It's simply a useful convention that indicates which event the function handles.
The response from the server is in the data property of the URLLoader object. You would construct a function something like the following code block: f u n c t i o n completeHandler(e:Event):void { var loadedXML:XML= X M L ( e . t a r g e t . d a t a ) ; / / d o something w i t h the loadedXML object
} The expression e . t a r g e t . d a t a finds the data property of the URLLoader object, and therefore the response from the server. This response is treated as a String. If the response comes as an XML document, you'll need to use the constructor method XML() to cast the response appropriately, as you can see in the preceding example.
285
CHAPTER 6
Handling errors Two events respond to errors in the request: • An i o E r r o r event is dispatched if the l o a d ( ) method results in a fatal error that ends the download. • A s e c u r i t y E r r o r event occurs if the SWF application tries to load data that is outside the application's security sandbox. You can add event handlers to respond in case either of these events is dispatched. In addition, if you're working over HTTP, you can use the HTTPStatus event to determine the status of the request. You can use this event to determine the HTTP status code for the request. The following code block shows how to assign handlers for these events. As with the other event listeners, you need to assign them before calling the l o a d ( ) method. loader.addEventListener(IOErrorEvent.I0_ERR0R, i o E r r o r H a n d l e r ) ; loader.addEventListener(SecurityErrorEvent,SECURITY_ERROR, ^»securityErrorHandler); loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, «-HTTPStatusHandler); loader.load(request); Each of these events will call the appropriate event handler, and you would need to add them as well. The examples that follow show very simplistic error handling with a t r a c e ( ) statement. In a production application, your event handling is likely to be a little more robust, perhaps displaying an Alert control with a detailed error message. function ioErrorHandler(e:IOErrorEvent):void { trace("ioErrorHandler: " + e);
} f u n c t i o n HTTPStatusHandler(e:HTTPStatusEvent):void { trace("HTTPStatusHandler: " + e ) ; trace("status: " + e.status);
}
function securityErrorHandler(e:SecurityErrorEvent):void { trace("securityErrorHandler: " + e);
}
Let's work through an example to show how to send a username and password to a server-side page so that a user can log in. The server-side page will send back one of two values: t r u e or f a l s e , indicating whether or not the user has successfully logged in.
Working through a URLLoader class example We'll work through the login example using the URLLoader class in both a Flash and a Flex application. As with the other examples in the book, the Flash application will use procedural code, and the Flex version will use a custom class file.
286
C O M M U N I C A T I N G W I T H THE SERVER
This example will work with an ASP.NET page written in VB .NET. I've also provided PHP and ColdFusion versions of the same server-side functionality, if you would prefer to work with either of those languages. Remember that to work through the examples, you'll need an application server capable of processing the server-side page. In my case, I'm using Microsoft's web server, IIS. You may also work with Apache or ColdFusion Server. The server-side page is very simple. It will check that the passed-in username and password match variables hard-coded within the file. We're not concerned with connecting to a database in this example, although you would use the same general approach as shown here. The server-side page will return a simple XML document containing one element: . This element will either contain the text t r u e or f a l s e . It's important to note that the server-side page receiving the request must know how it's going to receive the variables from the SWF application. Server-side pages use different approaches to access variables sent by the GET method from those that access variables sent by the POST method. In this example, we're using the POST method, although the GET method would suffice. I've chosen to use POST so that I can show you how to set the method property. The server-side page must also know in which format the variables will appear. Accessing XML content sent from the SWF application works differently compared with accessing name/value pairs. In this example, we'll send the variables through in name/value pairs. In the next section, I'll explain the server-side pages. I'm not providing a tutorial about how to write each server-side language, as you can find plenty of tutorials on the Web to help out. Rather, I'll walk through each of the server-side page examples so I can explain the code. We'll start with the simple VB. NET page. This page is written in VB .NET 3.5.
Understanding the VB .NET page The VB .NET page will check the variables sent from the user and generate a simple XML stream containing a response. In this section, I'll show you the declarative code for the page. I've actually used a code-behind page to keep the VB code separate from the interface. In my version, the VB code appears in the file login.aspx.vb. You can find this page with the chapter resources, along with the file login.aspx. The l o g i n . a s p x page contains only one line, as all of the processing code appears in the code-behind page login.aspx.vb. The single line in the l o g i n . a s p x page follows: This line declares that the page will be written in the VB language and that the events don't need to specify event handlers. It indicates that the code for the page exists in the l o g i n . a s p x . v b page, and that page inherits the class l o g i n , created in the code-behind file.
287
CHAPTER 6
The full code for the l o g i n . a s p x . v b page follows. I'll explain it after the listing. Imports System.10 Imports System.Xml P a r t i a l Class l o g i n I n h e r i t s System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, *-ByVal e As System.EventArgs) Handles Me.Load Dim strUsername As S t r i n g = "sas" Dim strPassword As S t r i n g = " s e c r e t " Dim strSuppliedUsername As S t r i n g = Request.Form("username") Dim strSuppliedPassword As S t r i n g = Request.Form("password") Dim strResponse As S t r i n g = "" Dim loginResponse As XmlTextWriter = New XmlTextWriter( ^»Response.OutputStream, Encoding.UTF8) If StrComp(strUsername, strSuppliedUsername) = 0 Then If StrComp(strPassword, strSuppliedPassword) = 0 Then strResponse = " t r u e " Else strResponse = " f a l s e " End I f Else strResponse = " f a l s e " End I f Response.ContentType = " t e x t / x m l " loginResponse.WriteStartDocument() loginResponse.WriteStartElement("loginResult") loginResponse.WriteValue(strResponse) loginResponse.WriteEndElementQ loginResponse.Flush() loginResponse.Close() End Sub End Class The code appears in a Page_Load subroutine so that it is called after the page finishes loading. The subroutine starts with statements that reference the relevant namespaces for the file. In this case, the page needs the System. 10 and System.Xml namespaces, so it can write an XML stream from this file. When the page loads, it declares a number of variables, as follows:
288
•
strUsername and strPassword: These variables store the master username and password. Normally, you would get this information from a database. In order to keep this example as simple as possible, the variables are declared within this file.
•
strSuppliedUsername and strSuppliedPassword: These variables request the username and password details from the SWF application. The SWF application will POST the values to the server-side pages, so the pages must request the details as if the application had sent details from a form.
C O M M U N I C A T I N G W I T H THE SERVER
•
strResponse: This variable will store either t r u e or f a l s e , depending on whether the user has provided the correct username and password. Normally, you would make this a Boolean variable. However, the value will be treated as a S t r i n g when the XML document loads the SWF application. It keeps things simple to treat the t r u e or f a l s e values as S t r i n g variables here as well.
•
loginResponse: This variable will store the XML content that will be returned to the SWF application.
After declaring the variables, the subroutine compares the supplied username and password with the values in the strUsername and strPassword variables. If both sets of values match, the value of the strResponse variable will be set to t r u e ; otherwise, it will be set to f a l s e . The page uses an XmlTextWriter object to create and structure the XML content. It specifies that the output will be an information stream, rather than a physical file, by using the Response.OutputStream setting. The XmlTextWriter also indicates that the XML document will use UTF-8 encoding. The VB .NET page sets the ContentType for the page to t e x t / x m l to indicate that it is returning an XML document. It calls several methods of the XmlTextWriter object to create the XML document. This subroutine calls the WriteStartDocumentQ method to write the XML declaration for the file. It then writes the opening element using the WriteStartElementQ method. It uses WriteValueQ to write the value of the strResponse variable inside the element, and calls the WriteEndElementQ method to write the closing tag. Finally, the subroutine calls the FlushQ method to write the content. It then calls the Close() method on the XmlTextWriter object. When it finishes, the page can return only one of the two following results to the SWF application: true false Both are very simple, well-formed XML documents containing a single root element. Figure 9-1 shows what appears in a web browser window when this page detects that the supplied and required values don't match. In fact, because of the way I've set up this file, if no values are supplied, the result will be a f a l s e value.
http://localhost/FOE/login.aspx - Windows Internet Explorer http://localhost/FOE/login.aspx File
Edit
•ù &
View
Favorites
Tools
| v I ¡Ë9
Ta
X 1 1 Live Search
Help
http : //localhost/FOE/login. aspx
fît *
•
S
*
»
Tools
limage -
This PHP example is also simple. It starts by declaring the content type for the document as t e x t / xml. The next lines declare the variables that the page will need. I've used the same names for these variables as in the VB .NET example. The $strUsername and $strPassword variables contain the values required for a successful login. Again, you would normally look these up in a database, but for simplicity, they are hard-coded here. The $strSuppliedUsername and $strSuppliedPassword variables will store the supplied values from the user. The page starts by declaring them as empty strings. Then it tests each POST value using i s s e t ( ) to see if it has been sent from the SWF application. If so, the page assigns the values to the $strSuppliedUsername and $strSuppliedPassword variables.
290
C O M M U N I C A T I N G W I T H THE SERVER
The page compares the required and supplied values using the strcasecmp() function. If the user supplies the correct values, the $strResponse variable contains the value t r u e ; otherwise, it contains the value f a l s e . The next line creates a new version 1.0 DomDocument object with UTF-8 encoding. This is the XML document that the page will return to the SWF application. The file creates a single element called using the createElementQ and appendChild() methods. The createElementQ method requires the name of the element as the first argument and has an optional second argument that indicates the content for the element. In this case, the content is the $strResponse variable, so the text between the opening and closing tags will be either t r u e or f a l s e . Finally, the page finishes by writing the XML document to the browser window using saveXML(). You can find this page saved as l o g i n . p h p with the other chapter resources. Running it in a web browser will produce the same outcome that you saw in Figure 9-1. The last server-side page we'll review is the ColdFusion example.
Understanding the ColdFusion page I've saved the ColdFusion version of this processing file as login.cfm, and you can find this file with the chapter resources. The contents of this page follow: < c f p r o c e s s i n g d i r e c t i v e suppresswhitespace="yes" pageencoding="utf-8"> < c f i f compare(strPasswordj strSuppliedPassword) E0 0 > true false false #xmlString#
291
CHAPTER 6
The page starts with a < c f p r o c e s s i n g d i r e c t i v e > tag, which provides the settings for the document. In this case, the page suppresses whitespace and uses UTF-8 encoding. As with the other examples, The first two variables relate successfully. As in the other you don't need to set a data
the page starts by declaring the variables it will use with tags. to the username and password that the user needs to provide to log in examples, these variables are hard-coded to keep things simple. Again, type for these variables in ColdFusion.
The page then sets default values to use for the username and password POST variables. The page finds these variables using FORM.username and FORM.password. The default values are empty strings. It's important to set the defaults, in case no variables are received from the SWF application. After setting the defaults, the page then uses to set the strSuppliedUsername and strSuppliedPassword variables to the value of the variables sent from the SWF application. The code will compare the supplied values when outputting the XML content. The page generates the XML content within the block. The block has the variable name loginReponse, which the page will use to output the XML tree to the web browser window. The first part of the XML content is an XML declaration. The block also includes the literal tags and . Inside these tags, the next lines use < c f i f > and tags with the comparenocase() function to compare the values of the user-supplied variables with those required for a successfully login. The variable will contain either the text t r u e or f a l s e . The code converts the simple XML document to a S t r i n g using the T o S t r i n g Q method. It then sets the content type of the document to t e x t / x m l and resets the buffer. Finally, it outputs the XML string using a element. You can use any one of the three server-side pages that I've covered here as you work through the example. You'll just need to remember to use the correct page name: login.aspx, login.php, or l o g i n . c f m and server path when you're entering the URL information. Let's work through a Flash example that uses server-side processing to log in a user. The SWF application will have a simple interface that collects the username and password from the user. The Flash example will use procedural code, while the Flex example that appears later will use a custom class.
Working through the Flash example In this example, the Flash application will collect the username and password from a user and send them to a server-side page for processing. The server-side page will check the user's credentials and inform the SWF application of the outcome in a simple XML document. This example will use a single URLLoader object to pass variables and receive the response. Note that it is possible to use multiple URLLoader objects if you need to separate the loading of content from the updating of content.
292
C O M M U N I C A T I N G W I T H THE SERVER
1. Create a new folder in your web server for the application and give it the name of your choosing. I called my folder FOE. Copy the relevant server-side file to that folder. I'm using the file l o g i n . a s p x in this example, so I needed to copy that file, as well as the bin folder containing the compiled VB .NET code. 2. Copy the file l o g i n . f l a from the resources to the folder on the web server. This file contains the interface for the simple login application. Figure 9-2 shows how it appears when viewed in Flash. Login Username
1
Password
1 C
Luy in
Figure 9-2. The interface for the login application
The interface consists of two Textlnput controls and a Button, along with a text field in which to display messages. The Password Textlnput has the displayAsPassword field set to true. 3. Add a new layer and call it actions. You'll use this for all of the ActionScript code. Open the new layer in the Actions panel with the F9 shortcut key. Add the following code at the top of the actions layer to set up the application: var sender:URLLoader; var sendPage:URLRequest; var sendVars:URLVariables; initApp stop(); These lines declare the variables for the application. You've seen this code before. The first variable, sender, is a URLLoader object that will send the request. The sendPage variable is a URLRequest object that details the page that the application will request. The variable sendVars is a URLVariables object that will pass the username and password from the SWF application to the server-side file. The final line calls the i n i t A p p ( ) function, which you'll create in the next step. 4. The SWF application will use the i n i t A p p ( ) function to create the URLLoader, URLRequest, and URLVariables objects to make the request. Add the following function to the actions layer, underneath the call to the i n i t A p p ( ) function. I'll explain it after the code. function initApp:void { var s e r v e r P a t h : S t r i n g = " h t t p : / / l o c a l h o s t / F O E / " ; var u r l : S t r i n g = serverPath + " l o g i n . a s p x " ; sender = new URLLoaderQ; sendPage = new URLRequest(url); sendPage.method = URLRequestMethod.POST; sendVars = new URLVariablesQ;
}
CHAPTER 6
The first two lines set up the URL of the server-side page. The serverPath is separated from the page name. You may need to change the value of the serverPath variable and server-side file name to reflect your own settings. As I discussed earlier, it's much easier if you set a variable for the path to the folder on the web server. You'll be able to change this value when you move the application to its production location. If you were going to use more than one URLLoader object, you would probably set this variable at the top of the actions layer, rather than as a local variable inside a function. The application sets the method of the URLRequest to URLRequestMethod.POST, as the server-side files are expecting a POST request. If the server-side files weren't specific about how they expected the variables, you could omit the line, and the application would use the default method URLRequestMethod.GET. 5. The SWF application will request the server-side page after the user enters a username and password and clicks the Log in button. You'll need to add an event listener that responds when the user clicks the button to start the process. Add the following line to the end of the i n i t A p p Q function: login_btn.addEventListener(MouseEvent.CLICK,
clickHandler);
This line adds the handler function c l i c k H a n d l e r Q to the l o g i n _ b t n instance as an event listener. The function will respond to the c l i c k event. You'll add the c l i c k H a n d l e r Q function shortly. 6. The application also needs to react when it receives a response from the URLLoader object indicating that the request is complete and that it has received a response. You need to add an event listener that listens for the complete event. Add the following line to the i n i t A p p Q function, below the previous line of code: sender.addEvent Listener(Event.COMPLETE,
completeHandler);
Again, you'll add this handler function shortly. 7. The application should trap two more events: i o E r r o r and s e c u r i t y E r r o r . These event handlers will provide some basic error-handling capability and display error messages to the user. Start by adding the following import statements at the top of the actions layer: import import
These statements import the two Event classes needed for the error handling. 8. Add the following event listeners in the i n i t A p p Q function. You'll add the functions themselves in a later step. sender.addEvent Listener(IOErrorEvent.I0_ERR0R, i o E r r o r H a n d l e r ) ; sender.addEvent Listener(SecurityErrorEvent.SECURITY_ERR0R, ^»securityErrorHandler); 9. Let's now move to the event handler functions, starting with the function to handle the Log in button click. Add the c l i c k H a n d l e r Q function below the i n i t A p p Q function. This is the function that assembles the variables and makes the request for the server-side page, passing the variables.
294
C O M M U N I C A T I N G W I T H THE SERVER
f u n c t i o n clickHandler(e:MouseEvent):void { var suppUsername:String = username_txt.text; var suppPassword:String = p a s s w o r d _ t x t . t e x t ; if (suppUsername.length > 0 && suppPassword.length > 0) { message_txt.text = " " ; sendVars.username = suppUsername; sendVars.password = suppPassword; sendPage.data = sendVars; sender.load(sendPage);
} else { message_txt.text = "You must enter both a username and password « b e f o r e c l i c k i n g the b u t t o n . "
}
}
This function retrieves the user entries from the usernamejtxt and passwordjtxt fields. It then tests both entries to check that values have been supplied using the length property. If both entries have a length greater than 0, the code block inside the if portion of the code block executes. If not, the code in the else portion executes. In that case, the application displays the message You must enter both a username and password before clicking the button in the message_txt instance. When the user provides both details, the function clears any existing messages in the message_txt text field. It then adds the username and password properties to the sendVars object, and assigns the relevant values from the user. These lines create the name/value pairs. Once the function assigns the values, the URLVariables object is set as the data property of the URLRequest object. This portion of the code block finishes by calling the l o a d ( ) method of the URLLoader object to make the request for the supplied URLRequest object, sendPage. 10. Add the completeHandler() function to respond when the application receives the server response. Remember that the response is a simple XML document containing one element: . This element contains one of two values for the text: t r u e or f a l s e . Add the following completeHandler() function to the actions layer: f u n c t i o n completeHandler(e:Event):void { var xmlResponse:XML = X M L ( e . t a r g e t . d a t a ) ; var userMessage:String; if ( x m l R e s p o n s e . t e x t ( ) . t o S t r i n g ( ) == " t r u e " ) { userMessage = "Congratulations. You were s u c c e s s f u l " ;
}
else { userMessage = "Login f a i l e d . Please t r y again";
}
message_txt.text = userMessage;
} The function receives an Event object, which it will use to locate the loaded data with the expression e . t a r g e t . d a t a . The function starts by assigning this property to the xmlResponse XML object. It uses the XML() constructor method to assign the value, because it needs to cast the loaded content as an XML object instead of leaving it as a String. The function also declares a variable, userMessage, which will contain the response to the user.
295
CHAPTER 6
The completeHandler() function locates the text in the server response using the t e x t ( ) method. Because there is a single element in the XML document, the function can apply the method to the XML object, which is equivalent to the root (and only) element in the document. The function tests the value of the response and sets the message string appropriately. Notice that this comparison is a string comparison. The function uses the t o S t r i n g ( ) method on the xmlResponse.text() expression because that expression returns an XMLList. In fact, because the XMLList consists of a single value that is text, you don't actually need to use t o S t r i n g ( ) . You could just use the expression xmlResponse. t o S t r i n g ( ) to find the text inside the element.
The compared value t r u e is a String, rather than a Boolean value, because all values from an XML document are treated as S t r i n g values by default. The function could have cast the returned value as a Boolean variable, but I think it's simpler to use the approach shown here. 11. The final step is to add the two error-handling functions that follow: function ioErrorHandler(e:IOErrorEvent):void { message_txt.text = e . t e x t ;
} function securityErrorHandler(e:SecurityErrorEvent):void { message_txt.text = e . t e x t ;
}
These functions display text to the messagejtxt control in the case of either an IOError or a SecurityError. Both functions display the t e x t property associated with the error. 12. Test the application in the Flash. The supplied server-side files use the username sas and password secret. Figure 9-3 shows what you should see if you provide the correct login values.
login, swf File
View
Control
Debug
e am
Login Username Password
sas
"**" Lcc; in
Congratulations. You were successful
Figure 9-3. The interface after a successful login attempt
296
C O M M U N I C A T I N G W I T H THE SERVER
13. Publish the file and run the created HTML file through the web browser. Don't forget to enter the full server path in the address bar. Figure 9-4 shows the application with an unsuccessful login. I've purposely included the address bar in the screenshot so you can double-check the path that you've used. If you want to check the error-handling features of the application, change the name of the file and use a server path in a different domain.
JÖÖ u
V r login - W i n d o w s Internet E x p l o r e r £3
"
File
Edit
&
i ' httpi/^ocatwsl^-Ufc^ogm.html view
Favorites
Tools
|M
*?
X
uvese»d!
Help
tfiogn
fit *
0
#
*
Tools -
»
A
Login Username
sa*
Password I
Loam
Login failed. Please tiy again
m
»•J Local fitranet
100%
-
Figure 9-4. The application in a web browser showing an unsuccessful login attempt
In this example, I showed you how to integrate a simple SWF application created in Flash with a server-side page. The example used the page login.aspx. Alternatively, you could have used the l o g i n . p h p or l o g i n . c f m page provided with the chapter resources. The complete code for the actions layer follows, in case you want to check it against your own file: import f l a s h . e v e n t s . I O E r r o r E v e n t ; import f l a s h . e v e n t s . S e c u r i t y E r r o r E v e n t ; var sender:URLLoader; var sendPage:URLRequest; var sendVars:URLVariables; initApp; function initApp:void { var s e r v e r P a t h : S t r i n g = " h t t p : / / l o c a l h o s t / F O E / " ; var u r l : S t r i n g = serverPath + " l o g i n . a s p x " ; sender = new URLLoaderQ; sendPage = new URLRequest(url); sendPage.method = URLRequestMethod.POST; sendVars = new URLVariablesQ; login_btn.addEventListener(MouseEvent.CLICK, c l i c k H a n d l e r ) ; sender.addEvent Listener(Event.COMPLETE, completeHandler); sender.addEvent Listener(IOErrorEvent.I0_ERR0R, i o E r r o r H a n d l e r ) ; sender.addEvent Listener(SecurityErrorEvent.SECURITY_ERROR, ^»securityErrorHandler);
} 297
CHAPTER 6
f u n c t i o n clickHandler(e:MouseEvent):void { var suppUsername:String = username_txt.text; var suppPassword:String = p a s s w o r d _ t x t . t e x t ; if (suppUsername.length > 0 && suppPassword.length > 0) { message_txt.text = " " ; sendVars.username = suppUsername; sendVars.password = suppPassword; sendPage.data = sendVars; sender.load(sendPage);
} else { message_txt.text = "You must enter both a username and «password before c l i c k i n g the b u t t o n . "
}
}
f u n c t i o n completeHandler(e:Event):void { var xmlResponse:XML = XML(e.target.data); var userMessage:String; if ( x m l R e s p o n s e . t e x t ( ) . t o S t r i n g ( ) == " t r u e " ) { userMessage = "Congratulations. You were s u c c e s s f u l " ;
}
else { userMessage = "Login f a i l e d . Please t r y again";
}
message_txt.text = userMessage;
} function ioErrorHandler(e:IOErrorEvent):void { message_txt.text = e . t e x t ;
}
function securityErrorHandler(e:SecurityErrorEvent):void { message_txt.text = e . t e x t ;
}
You can find my completed Flash file l o g i n _ c o m p l e t e d . f l a saved with your chapter resources. Let's move on to the Flex version of this example.
Working through the Flex example The Flex version of this example will achieve the same outcome as the Flash version, but use class-based code. It will work with the same server-side pages as the Flash example. I'm going to work with the VB. NET page server-side, but feel free to use the PHP or ColdFusion version. 1. Create a new Flex project and application file with the name of your choosing. Set the location to a folder in your web server. I'm using the FOE folder that I used in the previous example. This folder contains the relevant server-side file. 2. Create a new ActionScript class using the command File > New > ActionScript Class. Add the class to the x m l U t i l i t i e s package and call it XMLLoader. The file should contain the following:
298
C O M M U N I C A T I N G W I T H THE SERVER
package x m l l l t i l i t i e s { p u b l i c class XMLLoader { p u b l i c f u n c t i o n XMLLoaderQ { }
} 3. Add the following import statements above the class declaration: import import import import import import import
These import statements reflect the classes needed for this example. The IOErrorEvent and SecurityErrorEvent classes cover error handling. The A l e r t class allows the application to display simple errors from the class file. 4. Add a bindable declaration above the class declaration so that all public methods and properties will be bindable in the application file. [Bindable] 5. The code needs to declare some private variables for the URLLoader, URLRequest, and URLVariables objects used in the class file. It also needs to declare an XML object to store the server response. Add the following declarations below the class declaration: private private private private private
6. You'll need to modify the constructor method to receive the server path as an argument. The method also needs to create a new URLLoader and add an event listener that responds to the complete event. It finishes by adding event handlers for the error events IOError and SecurityError. Modify the code as shown in bold here: p u b l i c f u n c t i o n XMLLoader(path:String) { serverPath = path; sender = new URLLoader()j sender.addEventListener(Event.COMPLETE, completeHandler)j sender.addEventListener(IOErrorEvent.I0_ERR0R, IOErrorHandler)j sender.addEventListener(SecurityErrorEvent.SECURITY_ERROR, ^»securityErrorHandler)j
}
299
CHAPTER 6
The XMLLoaderQ method sets the value of the serverPath private variable to the path argument that the application file will supply. It creates a URLLoader object and adds an event listener that responds when the loading operation completes successfully. The event listener calls the completeHandler() method. The constructor method also adds event handlers to deal with the IOErrorEvent and SecurityErrorEvent events. You'll add those handler functions soon. 7. Add the first event handler, the completeHandler() method, as shown here: p r i v a t e f u n c t i o n completeHandler(e:Event):void { xmlResponse= X M L ( e . t a r g e t . d a t a ) ; dispatchEvent(new Event(Event.COMPLETE));
} This completeHandler() method assigns the returned XML content from the server-side file to the object xmlResponse, casting it to the type XML with the XML() constructor method. The method then dispatches the complete event so that the application will know that the server response is available. 8. Next, add the IOErrorHandlerQ method. private function I0ErrorHandler(e:I0ErrorEvent):void{ dispatchEvent(new IOErrorEvent (IOErrorEvent.I0_ERR0R, f a l s e , t r u e , «e.text));
} This method dispatches an IOErrorEvent to inform the application that there has been an IOError. It sends the text associated with the event in the dispatched event. The application will then be able to display an appropriate error message in the TextArea control. The other two arguments, bubbles and cancelable, have been left at their default values. 9. The final event handler is the s e c u r i t y E r r o r H a n d l e r ( ) method. Add the following private method to the class file: private function securityErrorHandler(e:SecurityErrorEvent):void{ dispatchEvent(new SecurityErrorEvent (SecurityErrorEvent. «SECURITYJRROR, f a l s e , t r u e , e . t e x t ) ) ;
} As with the previous handler function, this function dispatches an event to the application. This time, it's a SecurityErrorEvent. This dispatchEvent() method also passes the text associated with the error. 10. The code doesn't include a function to handle the request and associated variables. You need to create a public method that the application can call. The application will send the variables as an Object, set the method property of the URLLoader, and call its l o a d ( ) method. This public method, which I've named loadXML() will receive two arguments: the name of the server-side file to request and the variables to send with that request. The application will combine the server-side file name with the value of the serverPath variable to determine the full path to the file.
300
C O M M U N I C A T I N G W I T H THE SERVER
You could also pass in the full path to the loadXMLQ method, and the result would be the same. However, it's better to pass the values to the class file separately, in case the application works with more than one URLLoader object. That's not the situation here, but it's good practice to code so that it's easy to extend class files later if needed.
Add the loadXMLQ method now. p u b l i c f u n c t i o n loadXML(xmlFile:String, vars:URLVariables):void { try { sendPage = new URLRequest(serverPath + s e r v e r F i l e ) ; sendPage.data = vars; sendPage.method = URLRequestMethod.POST; sender.load(sendPage);
} catch ( e : E r r o r ) { A l e r t . s h o w ( " C a n ' t contact server side f i l e " ) ;
}
}
Again, the code uses a t r y / c a t c h block to provide some simple error handling. In the case of an error, the application will display the message Can't contact server side file in an Alert control. As I said earlier, you would probably make the error handling a little more robust in a real-world application. Instead of displaying an Alert, the class file could dispatch an event to the application. You could add also some checking to make sure that the server path includes a finishing forward slash (/). If this character is missing, the full path to the server-side file won't be correct. I'll leave you to think about those areas yourself! The loadXMLQ method creates a new URLRequest object, using the server path already assigned and the URL passed in as the first argument. The method assigns the variables argument to the data property of the URLRequest object and sets the method to POST the variables to the server-side page. The loadXMLQ method finishes by calling the l o a d Q method to make the request for the server-side file. 11. The XMLLoader class will need one public method to return the response from the server-side file. This file will return a single simple element: . Remember that the element will contain the text t r u e or f a l s e . The application will apply the t o S t r i n g Q method to locate the text inside this element when it receives the XML object from the XMLLoader object. Add the following public responseQ method to the class file: p u b l i c f u n c t i o n responseQ:XML { r e t u r n xmlResponse;
}
301
CHAPTER 9
You've now finished creating the class file. I've included the completed code so that you can check what you've done against my version. package x m l l l t i l i t i e s { import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLVariables; import flash.net.URLRequestMethod; import f l a s h . e v e n t s . I O E r r o r E v e n t ; import f l a s h . e v e n t s . S e c u r i t y E r r o r E v e n t ; import m x . c o n t r o l s . A l e r t ; [Bindable] p u b l i c class XMLLoader { p r i v a t e var sender:URLLoader; p r i v a t e var sendPage:URLRequest; p r i v a t e var sendVars:URLVariables; p r i v a t e var s e r v e r P a t h : S t r i n g ; p r i v a t e var xmlResponse:XML; p u b l i c f u n c t i o n XMLLoader(path:String) { serverPath = path; sender = new URLLoaderQ; sender.addEvent Listener(Event.COMPLETE, completeHandler); sender.addEvent Listener(IOErrorEvent.I0_ERR0R, ^-IOErrorHandler); sender.addEvent Listener(SecurityErrorEvent.SECURITY_ERR0R, ^»securityErrorHandler);
} p u b l i c f u n c t i o n loadXML(serverFile:String, *»vars:URLVariables):void { try { sendPage = new URLRequest(serverPath + s e r v e r F i l e ) ; sendPage.data = vars; sendPage.method = URLRequestMethod.POST; sender.load(sendPage);
}
catch ( e : E r r o r ) { A l e r t . s h o w ( " C a n ' t contact server side f i l e " ) ;
}
}
p u b l i c f u n c t i o n response():XML { r e t u r n xmlResponse;
}
p r i v a t e f u n c t i o n completeHandler(e:Event):void { xmlResponse = XML(e.target.data); dispatchEvent(new Event(Event.COMPLETE));
}
private function I0ErrorHandler(e:I0ErrorEvent):void{ dispatchEvent(new IOErrorEvent (IOErrorEvent.IOJRROR, *»true, e . t e x t ) ) ;
} 302
false,
C O M M U N I C A T I N G W I T H THE SERVER
private function securityErrorHandler(e:SecurityErrorEvent):void{ dispatchEvent(new S e c u r i t y E r r o r E v e n t ( «SecurityErrorEvent.SECURITY_ERRORj f a l s e , t r u e , e . t e x t ) ) ;
}
}
}
12. The next step is to create the Flex application file. Start by building the interface. Switch to the main application file and add the following interface elements inside the element: Figure 9-5 shows how the interface should appear in Design view. The interface includes Label controls as well as two Textlnput components where the user will enter a username and a password. The Password Textlnput has the displayAsPassword attribute set to t r u e to mask the password entry. The interface also includes a Log in button, which will trigger the request. Messages, including the response f r o m the server, will display in the TextArea control. Feel free to make changes to this interface. However, make sure that you don't change the id settings for the components.
Login Username Password
Figure 9-5. The Flex application interface for the URLLoader class example
13. As you've seen previously, a function called i n i t A p p ( ) will set up the application. It will be called in the creationComplete attribute of the element. Modify the element as shown here in bold to add the creationComplete attribute: < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" l a y o u t = " a b s o l u t e " creationComplete="initApp(event)">
303
CHAPTER 6
14. The i n i t A p p ( ) function and supporting code appear in the following block. Add it above the interface declarations. I'll explain it after the code. This block starts by importing the relevant classes. The import statements include the FlexEvent class, which is necessary because this event is dispatched when the interface finishes creating. The creationComplete handler i n i t A p p ( ) will receive this event as an argument. The code block needs to import the XMLLoader custom class that you just created. It also needs to import the classes for the two error events IOErrorEvent and SecurityErrorEvent. The function declares two variables, myXMLLoader, which is of the type XMLLoader, and serverPath. The serverPath variable contains the path to the application on the web server. You may need to change the value of this variable if your server path is different. The i n i t A p p ( ) function receives a FlexEvent as an argument. Even though the application won't use any properties of this Event, it is best practice to recognize that the function receives the Event. The function creates a new instance of the XMLLoader class using the constructor method. It passes the serverPath variable as an argument to this function. The i n i t A p p ( ) function also assigns four event handlers. The first three handler functions relate to the myXMLLoader object.
304
C O M M U N I C A T I N G W I T H THE SERVER
The first handler is the completeHandlerQ function, which is called when the URLLoader receives a response from the server-side page. Second is the IOErrorHandlerQ function, which responds when notified of an IOError, such as a missing server-side page or incorrect file path. The third function, s e c u r i t y E r r o r H a n d l e r Q , is dispatched when a security error occurs. This error might happen if the application tries to load external content from a sandbox different from that of the SWF application's own sandbox. The last event handler is the c l i c k H a n d l e r Q function. This function responds when the user clicks the Log in button. The code block includes the empty handler functions for each of these events as well. Even if they don't contain code, adding the empty functions will allow you to test the application without generating an error message. 15. When the user clicks the Log in button, the application needs to validate the username and password before it calls the loadXMLQ method of the XMLLoader object. The validation consists only of checking that the user has entered values. Once the application determines that entries have been made, it will call the loadXMLQ method. The loadXMLQ method passes the URL of the document to load, as well as a URLVariables object containing the entered username and password. Modify the Log in button c l i c k H a n d l e r Q function as shown here in bold: p r i v a t e f u n c t i o n clickHandler(e:MouseEvent):void { var username:String = username_txt.textj var password:String = password_txt.textj var myVars:URLVariables = new URLVariablesQj if (username.length > 0 && password.length > 0) { myVars.username = username; myVars.password = password; myXMLLoader.loadXML("login.aspx", myVars);
} else { message_txt.text = "You must enter both a username and password «before clicking the button"
}
}
The c l i c k H a n d l e r Q function starts by determining the values that the user has entered in the TextArea controls. It test the length of both the username and password entries to check that the length is greater than 0. If the function can validate these entries, it creates a new URLVariables object called myVars. It assigns the username and password properties from the entries in the Textlnput controls. The function then calls the loadXMLQ method, passing the name of the server-side document and the URLVariables object. The code example uses the server-side file login.aspx. If you're using a different file, you'll need to change the name of the server-side file in the loadXMLQ method. If the user leaves either Textlnput control blank, the message You must enter both a username and password before clicking the button displays in the TextArea at the bottom of the interface.
305
CHAPTER 6
16. The application needs to respond when it receives a reply from the web server. The reply could be a successful response from the server-side file or notification of an error. Let's deal with a successful response first. When the application receives a response, it will display it in the TextArea control. To have the completeHandler() function process the response, modify it as shown in bold here: p r i v a t e f u n c t i o n completeHandler(e:Event):void { var userMessage:Stringj var response:String = myXMLLoader.response().toString()j if (response == "true") { userMessage = "Congratulations. You were successful";
} This function starts by declaring two S t r i n g variables: one for the message that will display in the TextArea, and a second for the response received from the server-side page. It uses the t o S t r i n g ( ) method to obtain a S t r i n g representation of the XML content. Because the response is a simple XML element—that is, it doesn't contain anything other than text— the t o S t r i n g ( ) method returns only the text inside the element. The function tests the response from the server and compares it against the S t r i n g value true. I've stuck with strings rather than casting the returned value as a Boolean variable to keep things simple. The completeHandler() function creates one of two messages, depending on whether the user was able to log in successfully. It finishes by displaying the message in the TextArea control at the bottom of the interface. 17. The IOErrorHandlerQ and s e c u r i t y E r r o r H a n d l e r ( ) functions will deal with notifications of errors. In both cases, the t e x t property of the event will display in the messagejtxt control. Modify the handler functions as shown here in bold: p r i v a t e f u n c t i o n IOErrorHandler(e:IOErrorEvent):void { messagejtxt.text = e.textj
} private function securityErrorHandler(e:SecurityErrorEvent):void { messagejtxt.text = e.textj
}
These changes are self-explanatory. They access the t e x t property sent with the dispatched events. 18. Test the application. If Internet Explorer is your default browser, run the application to generate the test files and view the content directly from Flex Builder. If you're using Firefox, you will need to run the application to compile the files, and then copy them from the src folder to your web server folder. You can view the application by entering the URL in the web browser. Your URL should start with h t t p : / / , so in my example it would be h t t p : / / l o c a l h o s t . F O E / FlexApp/.
306
COMMUNICATING WITH THE SERVER After loading the HTML page in the browser, enter values for the username and password. The correct login values from the original server-side files are sas for the username and secret for the password. When you click the Log in button, you should see the TextArea update with a message. Figure 9-6 shows the effect of a successful login. Login Username Password
J T:: : : :
| C o n g r a t u l a t i o n s . Y o u were s u c c e s s f u l
F i g u r e 9 - 6 . The completed Flex application for the URLLoader class example
You can test if the error handlers work by changing the name of the server-side file to one that doesn't exist. You can also enter a domain outside the current SWF sandbox for further testing. Figure 9-7 shows the effect of using an incorrect file name.
F i g u r e 9 - 7 . The Flex application displays an error message.
The complete code for the application file follows: 0 && password.length > 0) { myVars.username = username; myVars.password = password; myXMLLoader.loadXML("login.aspx", myVars);
}
else { message_txt.text = "You must enter both a username and «password before c l i c k i n g the button"
}
}
private function I0ErrorHandler(e:IOErrorEvent):void { message_txt.text = e . t e x t ;
}
private function securityErrorHandler(e:SecurityErrorEvent): «void { message_txt.text = e . t e x t ;
]]>}
308
COMMUNICATING WITH THE SERVER You can find the resource files for this example with the other chapter resources saved as Login.mxml and XMLLoader.as.
As I mentioned earlier, the error handling in the Flex custom class file is not particularly robust. Feel free to modify the code to add more error handling. You might also want to modify the error-handler functions in the application file to display more user-friendly error messages. v
So far, this chapter has covered only how to work with the URLLoader class in Flash and Flex. Flex provides other approaches: using the element and HTTPService class. We'll turn our attention to that topic next.
Working with the element Both the element and HTTPService class allow you to request a URL from the server and receive a response. You can optionally send arguments with the request, perhaps if you need to filter the content or provide updates to a data source. You might also wish to have the variables processed on the server in some way. For example, the Flex application may require the server to generate an e-mail or save the values in a text file. The element exists in the mx.rpc.http.mxml package. As with the URLLoader class, when using the tag, the application must wait until it receives a response from the server before it starts to process the loaded content. The response could also be notification of a fault. Chapter 6 covers the element in detail. Before we work through an example using this element, let's have a quick refresher about how to send variables with the request.
Sending variables with the element As with the URLLoader class, you can send variables when you use the element to make a request for a server-side page. There are several different ways to send the variables with the request. First, you can send variables at the same time that you call the send() method. If you choose this approach, you pass an Object that contains the name/value variable pairs inside the send() method call. You can see an example of this approach in the following code block:
309
CHAPTER 10 < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="xmlService.send({username:'sas'})"> Here, the use of the curly braces indicates the creation of an Object. The object contains one name/ value pair. The variable is called username, and it has the value sas. Notice that the code uses single quotes to indicate that it is passing a S t r i n g value. You can also can use the element inside the element. The element lists the variables that you want to send. In the following code block, the element sends the same parameter, username, as shown in the previous example: The TextArea control uses curly braces binding syntax and accesses the response using the expression x m l S e r v i c e . l a s t R e s u l t , where x m l S e r v i c e is the id of the element. It sets this as the value for the t e x t property of the element. As an alternative, the tag includes an attribute called r e s u l t , which allows you to assign a result handler function to respond to the server response. In the following example, the r e s u l t H a n d l e r Q function will fire when the server provides a successful response: You would then need to write an ActionScript function called r e s u l t H a n d l e r Q to process the response. An example r e s u l t H a n d l e r Q function follows: function resultHandler(e:Event):void { var loadedXML:XML= e . t a r g e t . l a s t R e s u l t ; //do something with the loadedXML o b j e c t
}
The r e s u l t H a n d l e r Q function receives an event as an argument. The event object allows the function to access the l a s t R e s u l t property of the HTTPService object, which it identifies using t a r g e t . The function declares an XML object named loadedXML to store the response. This example sets the resultFormat for the request to e4x so it can assign the response directly to an XML object. The r e s u l t H a n d l e r Q function assigns the server response to the XML object using the expression e. t a r g e t , l a s t Result. The expression is equivalent to the root element of the loaded XML document. Because you've created an XML object, you can find specific parts of the response using E4X expressions or XML methods.
311
CHAPTER 10
Handling errors The element has another attribute, f a u l t , which allows you to assign a fault handler function in case of error. You can see an example shown in bold in the following element: Again, you would need to write an ActionScript function to respond to this event. The following is a sample f a u l t H a n d l e r ( ) function: function faultHandler(e:FaultEvent):void { trace(e.fault.message);
}
Working through an element example Let's work through the same example as in the previous exercise, where a user logs in to an application, and a server-side file checks the username and password. In this case, you'll see how to work with a tag-based approach using the element. 1. Create a new application file in Flex Builder and give it the name of your choosing. Make sure the application file is stored with the previous examples, in a folder on the web server. 2. Add the following interface elements: < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> The interface includes two Textlnput controls for the user's username and password. The Password control has the displayAsPassword attribute set to true. There is a Log in button to initiate the request to the server-side page. The messagejtxt control will display messages to the user, including the server response.
312
COMMUNICATING WITH THE SERVER
Figure 9-8 shows how the interface should appear in the Design view of Flex Builder. Login
F i g u r e 9 - 8 . The Flex application interface for the element example
3. Add the following tag above the opening component:
resultFormat="e4x"
This element has the id l o g i n S e r v i c e . The u r l attribute contains the full path to the web server folder—in my case. h t t p : / / l o c a l h o s t / F 0 E / . It also includes the file name l o g i n . a s p x . You may need to change this value to your own web server address and update the file name if you're using either the PHP or ColdFusion version. The code sets the value of the showBusyCursor attribute to true. This value will display a busy cursor while the request is in progress. It's handy for the user to know that the request is in progress. Unfortunately, this property isn't available when you script the HTTPService class. The element also contains the method="P0ST" attribute so that the variables will be sent using the POST method. All of the server-side pages provided expect that the value will be sent using this method. The code block includes both opening and closing tags because you'll add an element between them containing the username and password values. The element specifies e4x as the resultFormat because the server-side page will respond with an XML document. 4. The variables to send with the element will come from the user entries provided in the Username and Password controls. You'll use binding expressions to bind the values directly to the variables in the element. Modify the tag as shown in bold here: {username_txt.text} {password_txt.text} {username_txt,text} {password_txt,text}
315
CHAPTER 10 I've saved this example as LoginHTTPTag.mxml with the other chapter resource files. Let's move on to the HTTPService class.
Working with the HTTPService class in Flex The HTTPService class, in the mx. rpc. http package, works in a very similar way to the element. There are some minor differences between the two, which are covered in Chapter 6. You'll need to write ActionScript to work with the HTTPService class.
Sending variables with the HTTPService class If you are using the HTTPService class, you need to send the variables using ActionScript by including them in the send() method call, as follows: xmlService.send({username:
'sas'};
You can also add the variables in ActionScript using the following approach: var params:Object = new O b j e c t Q ; params.username = "sas"; xmlService.request = params;
316
COMMUNICATING WITH THE SERVER
Receiving a response When working with the HTTPService class, a successful response from the server dispatches the r e s u l t event. You would need to set an event handler to deal with this response. In the following example, the appropriate line appears in bold with the other code needed to script the HTTPService class: var serverPath:String = "http://localhost/FlexApp/"; v a r s e r v e r P a g e : S t r i n g = serverPath + " l o g i n . a p x " ; v a r params:Object = new O b j e c t Q ; v a r x m l S e r v i c e : HTTPService = new H T T P S e r v i c e Q ; x m l S e r v i c e . u r l = serverPage; xmlService.addEventListener(ResultEvent.RESULT, resultHandler); params.username = " s a s " ; params.password = " s e c r e t " ; x m l S e r v i c e . r e q u e s t = params; x m l S e r v i c e . r e s u l t F o r m a t = "e4x"; xmlService.method = URLRequestMethod.POST; xmlService.send() When the HTTPService dispatches the r e s u l t event, the r e s u l t H a n d l e r ( ) function processes the server reply. A successful response from the server can be accessed in the l a s t R e s u l t property of the HTTPService object. In order to access this response, you would construct a function like the one shown here: function resultHandler(e:Event):void { v a r loadedXML:XML= e . t a r g e t . l a s t R e s u l t ; //do something with the loadedXML o b j e c t
}
The expression e . t a r g e t . l a s t R e s u l t finds the server response. The code assigns this response to a variable called loadedXML. Because the code specifies a resultFormat of e4x, you don't need to cast the response.
Handling errors You can also assign an event listener that responds to the f a u l t event. The application dispatches this event when the request is not successful. The following code shows how to assign the event handler: xmlService.addEventListener(FaultEvent.FAULT,
faultHandler);
As with the r e s u l t event handler, you need to assign this function before calling the send() method. You would then need to create a f a u l t H a n d l e r Q function. A sample function follows: function faultHandler(e:FaultEvent):void { trace(e.fault.message);
}
317
CHAPTER 10 This example displays the message using a t r a c e ( ) statement. This approach is good for debugging, but not appropriate for a production application. The exercise in the next section uses an alternative approach.
Working through a HTTPService class example In this final example for the chapter, you'll build the same simple application you saw earlier, but you'll do so using the HTTPService class with a custom ActionScript class. You'll work in the same folder on the web server that you've used for the previous examples. 1. Use the File > New > ActionScript Class command to create a new ActionScript class. Add the class to the x m l U t i l i t i e s package and call it L o g i n S e r v i c e . The file should contain the following code: package xmlUtilities { public class LoginService { public function LoginServiceQ { }
}
}
2. Add the following import statements underneath the package declaration. These statements reference the classes that the application will need to use, including the two events you'll capture. You can also wait and see if the statements are added automatically when you declare the variables and write additional code a little later. import mx.rpc.http.HTTPService; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent;
3. Add a [ B i n d a b l e ] declaration above the class declaration. The code needs this declaration to make all of the public methods in the class file bindable in the application file. [Bindable]
4. Declare the following private variables below the class declaration: private var loginResponse:String; private var xmlService:HTTPService;
The first variable, loginResponse, will store the returned XML document. The second variable, xmlService, refers to the HTTPService object that will make the request from the server-side file. 5. Modify the constructor method L o g i n S e r v i c e Q as shown here: public function LoginServiceQ { xmlService = new HTTPServiceQ; xmlService.addEvent Listener(ResultEvent.RESULT, resultHandler); xmlService.addEvent Listener(FaultEvent.FAULT, faultHandler);
}
318
COMMUNICATING WITH THE SERVER The constructor method creates the HTTPService object and adds an event listener that responds when the object receives a r e s u l t event. This event is broadcast when the application receives the response from the server-side page. After receiving the event, the resultHandler() function will execute, processing the response. The method also adds another event listener to respond to the f a u l t event. 6. Add the resultHandler() private method shown here: private function resultHandler(e:ResultEvent):void { loginResult = e . t a r g e t . l a s t R e s u l t ; dispatchEvent(new ResultEvent(ResultEvent.RESULT));
}
The resultHandler() method is private because it will be needed only by the class file. The method sets the value of the loginResult XML object to the server response using the expression e . t a r g e t . l a s t R e s u l t . The method then dispatches the r e s u l t event to the application so that it will know that the request has finished and that a result has been received. 7. You also need to add the f a u l t H a n d l e r ( ) method to this class file. This method follows: private function f a u l t H a n d l e r ( e : F a u l t E v e n t ) : v o i d { dispatchEvent(new FaultEvent(FaultEvent.FAULT,false, true, n u l l , ^»nullj e.message));
}
This method dispatches a FaultEvent. It passes the e.message parameter so that the application file can display the correct error message in the interface. 8. The class file needs a public method that makes the request for the server-side file. I'll call the method sendRequest(). This method will receive two arguments: the name of the file to request and the variables to send with that request. For simplicity, the file name will need to include the server path. You could also send this in as another argument, if you want to store the value separately. public function sendRequest(xmlURL:String, v a r s : O b j e c t ) : v o i d { x m l S e r v i c e . u r l = xmlURL; xmlService.resultFormat = "e4x"; xmlService.request = v a r s ; xmlService.method = "POST"; xmlService.send();
}
The sendRequest() method creates a new HTTPService object, using the URL passed in as the first argument. Because you'll be using this with a server-side file, the xmlURL variable will need to contain a full http:// path. The public method sets the resultFormat property to e4x, as the application is expecting the server-side page to return an XML document. It assigns the vars argument to the request property of the xmlService object so it can send the variables. It also sets the method property to POST the variables to the requested page. The last line of this method calls the send() method of the HTTPService class to make the request.
319
CHAPTER 10 9. The LoginService class will need one public method to return the content from the external document that is stored in the variable loginResult. Add the following public getResponse() method to the class file: public function getResponse():XML { return l o g i n R e s u l t ;
}
The method returns the XML object provided by the server-side page. Don't forget that you populated the object in the resultHandler() method that you created earlier. That's all the code for this class file. I've included the complete file so that you can check it against what you've done. package x m l U t i l i t i e s { import mx.rpc.http.HTTPService; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; [Bindable] public c l a s s LoginService { private var loginResult:XML; private var xmlService:HTTPService; public function LoginServiceQ { xmlService = new HTTPServiceQ; xmlService.addEvent Listener(ResultEvent.RESULT,
}
resultHandler);
public function sendRequest(xmlURL:String, v a r s : O b j e c t ) : v o i d { x m l S e r v i c e . u r l = xmlURL; xmlService.resultFormat = "e4x"; xmlService.request = v a r s ; xmlService.method = "POST"; xmlService.send();
}
public function getResponse():XML { return l o g i n R e s u l t ;
}
private function resultHandler(e:ResultEvent):void { loginResult = e . t a r g e t . l a s t R e s u l t ; dispatchEvent(new ResultEvent(ResultEvent.RESULT));
}
private function f a u l t H a n d l e r ( e : F a u l t E v e n t ) : v o i d { dispatchEvent(new FaultEvent(FaultEvent.FAULT,false, true, « - n u l l , n u l l , e.message));
}
320
}
COMMUNICATING WITH THE SERVER
10. You'll use this custom class in a Flex application file, so create a new file with the name of your choosing. Add the following interface elements: < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> The preceding code block creates the interface shown in Figure 9-10. This is the same interface that you saw in Figure 9-8. Login
F i g u r e 9 - 1 0 . The Flex application interface for the HTTPService class example
11. The code will initialize the application by calling a function named i n i t A p p Q in the creationComplete attribute of the element. This function will set up the necessary variables, including a new L o g i n S e r v i c e object. Modify the element as shown here in bold: < m x : A p p l i c a t i o n xmlns:mx="http://www.adobe.com/2006/mxml" l a y o u t = " a b s o l u t e " creationComplete="initApp(event)"> This function call will pass a FlexEvent.
321
CHAPTER 10 12. Add the following block, which includes the initAppQ function:
This block starts by importing the relevant classes. If you forget to add these lines, the import statements should be added automatically when you refer to the classes in the remaining code. The application will need the FlexEvent class, as this is the event type dispatched when the interface finishes creating. It imports the ResultEvent class to deal with the r e s u l t event dispatched by the HTTPService element. Similarly, it imports the FaultEvent class because it needs to handle the f a u l t event of the HTTPService element. The last import statement deals with the custom class that you just created, LoginService. The code creates a LoginService object called myLoginService. It declares the initAppQ function, which receives a FlexEvent as an argument. The function creates a new instance of the LoginService class and then assigns the resultHandlerQ function to be called when the LoginService object finishes requesting the external document and receives the server response. It also assigns the f a u l t H a n d l e r ( ) function, which will respond if there is an error. At the moment, neither the resultHandlerQ nor faultHandlerQ function contains any code. The code includes these function signatures to avoid errors in case you decide to run the application. The initAppQ function assigns an event listener to the login_btn instance. This listener responds to the c l i c k event of the button with the c l i c k H a n d l e r Q function. The block also contains an empty c l i c k H a n d l e r Q function.
322
COMMUNICATING WITH THE SERVER
13. When the user clicks the Log in button, the application needs to check that a username and password have been provided before it calls the sendRequest() method of the L o g i n S e r v i c e object. It will do this by testing that both entries have a length greater than 0. Modify the c l i c k H a n d l e r ( ) function as shown here in bold: private function clickHandler(e:MouseEvent):void { var username:String = username_txt.text; var password:String = password_txt.text if (username.length > 0 && password.length > 0) { var myVars:Object = new ObjectQ; myVars.username = username; myVars.password = password; myLoginService.sendRequest( «"http://localhost/FOE/login.aspx", myVars);
}
else { message_txt.text = "You must enter both a username and password «before clicking the button"
}
}
The c l i c k H a n d l e r Q function starts by assigning the values provided by the user to two variables: username and password. It then tests these variables to check that the length of both entries is greater than 0. If this is the case, the user must have entered values in both Textlnput controls. The function creates a new Object and assigns the username and password variables. It then calls the sendRequestQ method of the L o g i n S e r v i c e object, passing the URL to request, as well as the Object containing the variables. The URL of the server-side file contains the full path on the web server. You may need to change the path from what is shown in the code sample to one that reflects your own settings. If you're using either the PHP or ColdFusion example, you'll also need to change the file name. If either of the Textlnput controls does not contain an entry, the message You must enter both a username and password before clicking the button displays in the TextArea at the bottom of the interface. 14. The last step in building this application is to respond when a reply or fault is received from the server-side file. The application will display a successful response in the TextArea control using the r e s u l t H a n d l e r ( ) function. Modify this function as shown in bold: private function resultHandler(e:ResultEvent):void { var response:String = myLoginService.getResponse().toString(); var userMessage:String; if (response == "true") { userMessage = "Congratulations. You were successful";
The function starts by populating the response variable with a S t r i n g representation of the server response. It finds this using the public getResponseQ method, which returns an XML object. Because the XML object contains a single element, the t o S t r i n g ( ) method will find the text inside that element, which is either t r u e or f a l s e . The function tests the response and populates a S t r i n g variable with an appropriate message. It then displays this message in the TextArea. 15. Let's finish with the f a u l t H a n d l e r Q method. Modify it as shown in bold in the following code block: private function faultHandler(e:FaultEvent):void { message_txt.text = e.message.toString()j
}
16. You've now finished the Flex application file. Run the file and enter values for a username and password. If you want to test for a successful response, enter the username sas and password secret. Use other values if you want to see an unsuccessful response. When you click the button, you should see the TextArea update, as shown in Figure 9-11. Unlike in the previous tag-based approach, you won't see a busy cursor while the request is in progress. V-? C:\lretpub\wwwroot\FOE\bir-debug\LogirHTTPTag.html T
File A
l é C:tynetpub^wwwroot^OEVîin-debLigV-ogiriKTTFTag.html
Edit &
View
Favorites
Tools
Help
^ C : Vnetpub \ w u w o o t ^ O E Viin-debug l|_oginHTTFTag. h..
Login U Bernante
;.
Password I
Log in £
Congratulations. You v/ere successful
F i g u r e 9-11. The completed Flex application for the HTTPService class example
If you want to test the error handling, change to the name of the server-side file to one that doesn't exist in the folder. Figure 9-12 shows the effect. You may wish to replace this message with a more user-friendly error message!
324
COMMUNICATING WITH THE SERVER
Login Usern a me Password Log in I tmx.messagir
n e s s a g e s : : Erro rM e s s a g
body = (Object) #1 clientld = "DirectHTTPChannelO"
F i g u r e 9 - 1 2 . The Flex application displays a fault.
The complete code for the application file follows, in case you want to check it against your own application: 0 && password.length > 0) { var myVars:Object = new O b j e c t Q ; myVars.username = username; myVars.password = password; myLoginService.sendRequest( *-"http://localhost/FOE/login.aspx", myVars);
} else { message_txt.text = "You must enter both a username and ^»password before clicking the button"
]]>}
}
Y o u can find the resource files f o r this e x a m p l e with the other c h a p t e r resources saved as L o g i n H T T P C l a s s . m x m l and L o g i n S e r v i c e . a s .
Choosing the Flex approach Flex provides y o u with three a p p r o a c h e s w h e n it c o m e s to c o m m u n i c a t i n g with the server. Y o u can w o r k in A c t i o n S c r i p t with the URL L o a d e r or the H T T P S e r v i c e class, or y o u c a n use a t a g - b a s e d a p p r o a c h with the < m x : H T T P S e r v i c e > element. H o w d o y o u k n o w w h i c h t o c h o o s e ? If y o u prefer to take a t a g - b a s e d a p p r o a c h or y o u ' r e w o r k i n g with v e r y simple c o n t e n t , c h o o s e the < m x : H T T P S e r v i c e > e l e m e n t . Y o u w o n ' t need to write any c o d e , and y o u ' l l be able to use s i m p l e b i n d ing expressions to display the c o n t e n t in the application interface.
326
COMMUNICATING WITH THE SERVER However, if you prefer to work with ActionScript, you'll need to choose either the URLLoader or HTTPService class. You'll need to cast your returned content as XML before you can use E4X expressions with the URLLoader. You can specify this format specifically when working with the HTTPService class. The URLLoader class allows you to monitor the progress of your request using the bytesLoaded and bytesTotal properties. These properties aren't available with the HTTPService class. The URLLoader class also gives you access to a wider range of events. This can provide more detailed error handling than with the HTTPService class. So if you want a little more control in your Flex application, writing ActionScript with the URLLoader class is the best approach. Chapter 5 demonstrated how to do this.
Summary In this chapter, I showed you how to integrate SWF applications with server-side pages. You saw examples of a server-side page in VB .NET, PHP, and ColdFusion. We worked through applications in both Flash and Flex that used the URLLoader class to send variables and receive a response from the server. You also saw how to achieve the same result with the element and HTTPService class. In the next chapter, I'll show you how to work with web services in ActionScript 3.0 in both Flash and Flex.
327
Chapter 10
CONSUMING WEB SERVICES WITH FLEX A web service is a remote procedure that provides results in XML format. It's a bit like running a public function on someone else's computer. The user can call the procedure without needing to know anything about the way the remote system works or the way it stores the information. When users access data from a web service, they are said to consume the web service. A web service allows an organization to make its data publicly available without providing a user with direct access to its systems. Using a web service provides a layer of abstraction between a user and corporate systems. You might use a web service in an application to look up a currency exchange rate, to access local weather information, or even as a way to connect to in-house information in a structured way. The key point is that the information is usually provided from remote computers; that is, computers running at a different location from the current application. There are many different types of web services, including representational state transfer (REST), XML-Remote Procedure Call (XML-RPC), Simple Object Access Protocol (SOAP), and JavaScript Object Notation Remote Procedure Call ( J S O N - R P C ) . These represent different ways for users to access the remote service. SWF applications built in Flex can access SOAP web services using the element and WebService class. In this chapter, I'll go through both of these approaches and we'll work through two examples, showing different ways to consume the same web service. The web service we'll consume in the examples is a currency conversion service. We'll build an application that retrieves the rate and converts an amount.
329
CHAPTER 10 Flash doesn't have access to the WebService class. However, there are some alternative ways that you can use a SWF application built in Flash to consume a web service. That's the topic for the next chapter, so skip ahead if you're particularly interested in Flash. You can download the resources for the chapter from http://www.-Friendsofed.com. Before we explore how to consume a web service in a Flex application, it's important to understand some background information. The first section in this chapter explains some of the concepts associated with web services and some of the terms that you're likely to come across.
Understanding web services As I mentioned, there are many different standards or protocols for working with web services, including REST, SOAP, XML-RPC, and JSON-RPC. By the broadest definition, you could also consider an RSS feed to be a web service. All of these approaches provide access to information in a different way. Each uses a different vocabulary of XML to return the content. One of the most popular protocols for web services is SOAP. It is also the most relevant for this book because it is the only protocol supported by Flash and Flex. Because the WebService class in Flex works with SOAP web services, we'll start with that topic.
Understanding SOAP web services SOAP is a communication protocol that uses XML to describe web services. It uses a system of messages. SOAP messages occur in two directions, and are requests made to and responses received from a web service. The SOAP protocol comes in different versions, and the latest is version 1.2. It describes the specific XML vocabulary required to make the request and provide the response. If you're interested in finding out more about SOAP, you can read about SOAP 1.2 at the W3C web site in the following pages: • S O A P primer: http://www.w3.org/TR/2003/REC-soapi2-part0-20030624/ • Messaging framework: http://www.w3.org/TR/2003/REC-soapi2-partl-20030624/ • Adjuncts: http://www.w3.org/TR/2003/REC-soapi2-part2-20030624/ SWF applications built in Flex can access SOAP web services using the WebService class. Flex applications can use the element and WebService class to create the SOAP message for the request automatically. They can also decode the SOAP response from the web service. In order to be able to use a web service in a Flex application, you must be able to specify a Web Services Description Language (WSDL) file. The WSDL file provides the details of the functions the web service provides, how to access those functions, and how the returned data will be provided. The functions available at the web service are described as operations within the WSDL file. This file describes which parameters each operation requires, as well as the relevant data types. It also describes which return values each operation will provide and their data types. As well as built-in data types, the WSDL file can also define custom data types.
330
CONSUMING WEB SERVICES WITH FLEX
Understanding the role of WSDL WSDL is a vocabulary of XML, so you can view the contents of a WSDL file by loading the URL into a web browser. Figure 10-1 shows a sample WSDL file. UIHJS
l.tty://«*w.yfebseiv it ex _ c urn/C u rifimyCuiw v r^tir. C O P - C o l o m b i a n PesoKMF-Comoros FrancCRC-Costa Rica ColonHRKCroatian KunaCUl'-Cuban I'cso < b r > C Y l ' - C y p r u s PoundCZK-Czcch KorunaDKK-Danish KroncDJl~ Dijibouti TrancDOP Dominican PesoXCD Cast Caribbean DollarCGP Egyptian PoundSVC CI Salvador ColonFFK-Fstnninn KrnonFTB-Fthlopian BirrFllR-FuroFKP- Falkland IslnnHs PouudGMD-Gambiau DaldsiGHC-Gltariian CediGIP-Gibrallar PoundXAU-Gold OuncesGTQGu ate ma la QuetzatGNF-Guinea FrancGYD-Guyana DoltarHTG-Haiti GourdeHNL-Honduras L e m p i r a < b r > H K U - H o n g Kong DollarHDl--Hungarian l-orintlSK-lccland KronaiNR-indian RupeeCbrMDR-Tndnnesian Rupia h < h r > T Q n - I r a q i DinarTI S-Tsraeli S h e k e l < h r > 1 M D - l a m a t c a n D o l l a r < h r > l P Y Japanese YenJOD-Jordanian DinarKZT-Kazakhstan Tenge KES-Kenyan Sliill ¡rig KRW-Korean w o n < b r > K W D - K u w a i t i DlnarLAK-Lao KlpLVL-Latvian LatLBP-Lebanese Pound LSL-Lesotho LotiLRU-Libcrian DollarLYD-Libyan DinarLlL-Lithuanian L i t a < b r > H O P - M a c a u l ' a t a c a < b r > M K D Macedonian D e n a r < b r > M G r Malagasy TrancMWK Malawi KwachaMYR Malaysian RinggitMVR Maldives Ruf]yaaMTl - M a l t e s e 1 iraMRO-Manritania OugijlyaMIIR-Mauritius Rupee-i h r > M X N - M e x i r a n P e s o < b r > M D L - M o l d o v a i i LeuMNT-Mongolian T u g r i k < b r > H A D - H o r o c c a n D i r h a m < b r > M Z H - M o z a m b i q u e M e t l c a l < b r > H M K - H y a n m a r K y a t < b r > N A D - N a m l b l a n DollarNPR-Nepalese RupeeANG-Neth Antilles GuilderNZO New Zealand DollarNIO Nicaragua CordobaNGN Nigerian NairaKPW North Korean WnnNOK-Norweginn KrnneOMR-Omani RinlXPF-Pnrifir FrancC hr!>PKR-Pakistani RnpeeXPDPalladiurn OuncesPAB-Paneiuia BalboaPGK-Papud New Guinea KiuaPYG-Paraguayan GuarariiPENPeruvian Nuevo Sol PHP-Philippine PesoXPT- Platin um Ounces PLN-Polish Z l o t y < b r > Q A R - Q a t a r RialROL-Romanian LctiRUB-Russian R o u b l c < b r > W S f - S a m o a l a l a < b r > 5 l D - S a o i o n i c Dobra SAR-Saudi Arabian RiyaKbr>SCR Seychelles RupeeSLL Sierra Leone LeoneXAG Silver OuncesSGD Singapore DollarSKK-Slovak Kori inn STT-Slovenian TolarSRD-Solomon Islands DollarSQS-Sooinli ShillinqZAR-South African RandLKR-Sri Larika RupeeSHP-SL Helena PoundSDD-Sudanese DinarSRC-Surinam GuildcrSZL-Swaziland LilagcniSLK-Swcdish Krona I K Y - t u r k e y LiraCHFSwiss l~rancSYP Svrian PoundTWD Taiwan DollarTZS Tanzanian ShillinaTI IB Thai BahtTOP
-
=
-
% 100% ~
F i g u r e 1 0 - 1 . Viewing a WSDL file in a web browser
This WSDL file is for a currency conversion web service and is at the URL http://www.webservi.cex. net/CurrencyConvertor,asmx?WSDL. A WSDL file contains a set of definitions. It defines the data types used for the operations, the messages that it can receive and send, the operations available, and the communication protocols that the web service uses. As far as developers are concerned, the most important parts within the WSDL file are the elements. These elements contain details about the operation request and response, including the name of the operation. In Figure 10-1, you can see that the operation displayed is called ConversionRate. The element provides a description of the operation with supporting information. The WSDL file indicates that the ConversionRate operation requires that the following element is sent with the request:
CHAPTER 10 This argument is defined as part of the tns namespace, and it represents a custom data type. Further digging in the WSDL file reveals that the tns namespace is associated with the http://www. webserviceX.NET/ URL The WSDL file defines the ConversionRate data type as a complex element made up of FromCurrency and ToCurrency values. Each element can appear only once in the data type. This means that the user needs to provide two arguments when calling the ConversionRate operation. The following code block shows the definition of the ConversionRate data type from the WSDL file: You'll notice that this definition also specifies that both the FromCurrency and ToCurrency elements are of the type tns ¡Currency. The WSDL file defines this as a simple type element containing a list of abbreviations for acceptable currencies. The relevant element in the WSDL file follows, but I've included only the first two values in the list: < s : r e s t r i c t i o n base="s:string"> «¡enumeration value="AFA"/> «¡enumeration value="ALL"/> For developers, this data type means that they must send the correct abbreviation when they query the web service. The abbreviation must be one of the values in the elements. The web service returns a single element described in the following element: The WSDL defines the ConversionRateSoapOut message as a element. The element is a complex type element made up of a single element called ConversionRateResult, which is of the type double. The following code block shows the details of this element:
332
CONSUMING WEB SERVICES WITH FLEX You can see that finding your way through a WSDL file can be very complicated! Luckily, Flex can decode the contents of the file so that you don't need to understand it in any detail. The element and WebService ActionScript 3.0 class provide all the functionality that you need to work with SOAP web services in Flex.
Using Flex to consume a web service You can use the element and WebService class to make requests of a SOAP web service in any Flex application. You'll need to provide the WSDL of the web service, the name of any operations that you want to call, and the parameters required by those operations. The SOAP web service will then provide a response, which is either the returned value or a fault. You can use a tag-based or scripted approach. I'll show you both approaches, starting with an overview of the element.
Working with the element The element consumes SOAP web services and provide a response to a Flex application. To consume a web service with the element, you must take the following steps: 1. Create the element, providing a valid wsdl attribute. 2. Specify the web service operation(s) and identify the parameters to send. 3. Call the send() method of the web service. 4. Process the response from the web service. We'll look at each of these steps in turn.
Creating the web service request You can create a web service request using the element. You'll need to give the element an id and specify the URL for the WSDL file in the wsdl attribute, as shown here:
333
CHAPTER 10 You may also want to display a busy cursor while the request is taking place. You can do so using the following showBusyCursor attribute:
This attribute is useful as it informs the user that the request is in progress. It is available only to the element, and you can't set this property in the WebService class with ActionScript. Once you've set up the tag, you need to specify which operation to call from the web service.
Specifying the operation Each type of remote procedure available in the web service is called an operation, and you need to specify which operations you want to call from the web service. As I showed you earlier, it's possible to identify the operation names from the WSDL file. Even though you might add a single element, it's possible for the application to call multiple operations within that web service. You add each operation that you want to access using an element between the opening and closing elements. You then specify the arguments for that operation with the element. Every argument that you want to send for a specific operation appears inside the element, as shown here: Value l Value 2
The values that you send with an operation frequently come from Ul components in the application. When working with a tag-based approach, it's often easiest to access them with binding expressions. The following code shows how you might use bindings when specifying the arguments for the operation: {controlID.boundProperty} {controlID.boundProperty}
Once you've added the operation, you need to make the request.
Making the request You call the send() method of the web service to make the request. You'll need to refer to the id of the element and the operation name, as shown here: wsRequest.operationName.send();
334
CONSUMING WEB SERVICES WITH FLEX It's important to include the operation name, as it's possible to add multiple operations inside the element. If you need the response to be available when the application loads, you can add a call to the send() method to the creationComplete event of the element, as shown here: You could also make the request in the c l i c k attribute of a button, as you can see in the following line: Requesting the web service will result in a response that contains either the results of the request or an error message.
Receiving the response The web service will respond to the request in one of two ways: it will provide a valid response, or it will notify the application that a fault occurred. You can specify handlers for the r e s u l t of an operation by adding the r e s u l t attribute to the opening element, as shown here: You can track f a u l t events from the element, as shown in the following code block: You'll need to write ActionScript functions of the same name. In the preceding example, the r e s u l t H a n d l e r Q function receives a ResultEvent as an argument, while the f a u l t H a n d l e r ( ) function receives a FaultEvent. If you want to avoid writing ActionScript, you can also bind the response—whether it's a r e s u l t or f a u l t — d i r e c t l y to another component.
Accessing the reply As with the element, you can identify the returned results from the web service by accessing the l a s t R e s u l t property of the operation. If the request returns a single parameter, it's also possible to access the response using the t o S t r i n g method: wsRequest. operationName. lastResult. toStringQ; If the response returns more than one parameter, you can use the name of the returned parameter to identify which value to display. wsRequest.operationName.lastResult.returnedParam;
335
CHAPTER 10 Again, you must use the operation name to specify which result you're identifying. This is still the case, even if you've included only one operation in the element. You'll process the response in the r e s u l t H a n d l e r Q function either with ActionScript or by using a binding expression in another component. A simple binding expression follows:
You'll see examples of both approaches in this chapter.
Understanding the resultFormat of an operation The web service provides a result in the l a s t R e s u l t property of the operation. By default, the data that the web service returns is represented as a simple tree of ActionScript objects. You can also use E4X expressions to access the results; in this case, you'll need to declare the resultFormat of the operation as e4x.
The the you the
r e s u l t F o r m a t indicates how you'll access the response from the web service. In E4X expressions, l a s t R e s u l t property is equivalent to the root element, so you wouldn't include it in the path. If accept the default r e s u l t F o r m a t setting, you'll need to include the name of the root element in path.
Be very careful before using a resultFormat of e4x, as you will probably need to deal with namespaces in the response. If the web service response contains a default namespace, you'll need to write some additional ActionScript declaring the namespace. For example, the following opening element of a web service response contains the default namespace http://www.webserviceX.NET/. The default namespace doesn't have a prefix and appears in bold in the following code block:
To work with the result using E4X expressions, you would need to specify that you wanted to use the default namespace in an block, as shown here:
336
CONSUMING WEB SERVICES WITH FLEX
Handling errors You can handle errors from the web service in the -FaultHandler() function. This function receives a FaultEvent as an argument, and you can access the f a u l t property of the event. If you wanted to see a S t r i n g representation of the error, you would use the following code: FaultEvent.fault.faultString;
You can add this property to the f a u l t attribute of the element, as shown in the following line: This example displays the error message in an Alert control. That's enough of the theory. We'll work through an example so you can see the different ways to consume a web service.
Working through a tag-based example This example demonstrates how to consume a web service that provides currency conversion services. The WSDL file for the web service is located at http://www.webservicex.net/CurrencyConvertor. asmx?WSDL. Figure 10-1, earlier in the chapter, shows this WSDL file viewed in a web browser. We'll use the ConversionRate operation. This operation takes two parameters: the abbreviations of the currencies to convert from and to. These parameters are called FromCurrency and ToCurrency. The WSDL file provides a long list of currency abbreviations that you can use. The operation returns a double number called ConversionRate, but because it returns only a single value, you can use the t o S t r i n g ( ) method to access it. 1. Start by creating a Flex project with the name and location of your choosing. Create a new application file with the following interface:
337
CHAPTER 10 The application contains two ComboBox controls, which you'll populate with names of the currencies available for conversion. The data associated with each name will be the currency abbreviation. The interface also contains a Textlnput control, so that the users can enter the amount of currency that they wish to convert. There is a Convert button to carry out the operation, and a second Textlnput control to display the converted amount. Figure 10-2 shows how the interface appears when you run the application in a web browser. C u r r e n c y converter From currency To currency Amount
Converted amount
F i g u r e 1 0 - 2 . The currency converter interface
You need to add the list of currencies to convert from and to. You can see the full list of all abbreviations available to the web service at http://www.webservicex.com/CurrencyConvertor. asmx?op=ConversionRate. However, for simplicity, this example uses a small number of these currencies. Feel free to expand on the list if you like. Modify the from_cbo element as shown here in bold: Hkv
F i g u r e 1 0 - 6 . Displaying an error in the w e b service request
T h e application displays an Alert control with an error message. In this case, it says HTTP request error. Y o u could also include a more descriptive message in the - F a u l t H a n d l e r ( ) function. T h e complete c o d e for this application follows: {from_cbo.selectedItem.data} {to_cbo.selectedItem.data}
342
CONSUMING WEB SERVICES WITH FLEX
343
CHAPTER 10
You can find the completed file saved as CurrencyConverterTag.mxml with the chapter resources. Before we work though a scripted version of this example, let's explore the WebService class.
Working with the WebService class The WebService class is in the mx.rpc.soap.mxml package. It works in a very similar way to the tag, except that the showBusyCursor property isn't available to the class. The WebService class works with the Operation class, which describes the operations available to the web service. Let's look at the properties, methods, and events of the WebService class. Then I'll cover some relevant points about web service operations.
Properties of the WebService class Table 10-1 shows the most commonly used properties of the WebService class. T a b l e 1 0 - 1 . Properties of the WebService class
344
Property
Data type
Description
Default value
concurrency
String
Indicates how to handle multiple calls to the same service. Choose from multiple, single, and l a s t .
multiple
description
String
Provides the description of the service.
headers
Array
Returns the SOAP headers registered for the web service.
port
String
Specifies the port to use with the web service.
ready
Boolean
Indicates whether the web service is ready for requests.
service
String
Specifies which remote operation the web service should use.
wsdl
String
Specifies the location for the WSDL for the web service.
CONSUMING WEB SERVICES WITH FLEX For simple web services, you'll most commonly use only the wsdl property to set the location of the WSDL file for the web service. For that reason, I won't go into detail about the other properties, with the exception of concurrency. The concurrency property determines how to deal with multiple requests to the web service. The default value of multiple indicates that the application can call the web service more than once. A value of s i n g l e means that only one request can be made at a time and that a fault is generated when more than one request occurs. The value l a s t means that a new request cancels any earlier request. You would change from the default value only if you wanted to restrict the application from making multiple requests. The WebService class also has a number of methods.
Methods of the WebService class Table 10-2 shows the main methods of the WebService class. T a b l e 10-2. Methods of the WebService class
Description
Returns
canLoadWSDLQ
Determines if the web service is ready to load the WSDL file
Boolean
disconnect()
Disconnects from the web service and removes any pending requests
Nothing
Method
Parameters
getOperation()
name: S t r i n g
Returns the operation specified from the web service
AbstractOperation
loadWSDLQ
uri:
Loads the WSDL for the web service
Nothing
WebServiceQ
Constructor that creates a new web service
String
Nothing
When working with simple web services, you'll most likely use only the constructor and loadWSDL() methods. You can either pass the URL of the WSDL file with the loadWSDL() method or set the wsdl property prior to calling the method. Let's move onto the events dispatched by the WebService class.
Events of the WebService class The WebService tag dispatches a number of events, as summarized in Table 10-3.
345
CHAPTER 10 T a b l e 1 0 - 3 . The events dispatched by the WebService class
Event
Type
Description
fault
FaultEvent
Dispatched when a WebService call fails
invoke
InvokeEvent
Dispatched when the WebService call is invoked, providing an error isn't encountered first
load
LoadEvent
Dispatched when the WSDL loads successfully
result
ResultEvent
Dispatched when a WebService call returns successfully
Of these, you're most likely to use the f a u l t and r e s u l t events. Before we move on, it's worth looking briefly at the Operation class, which works with the WebService class when consuming a web service.
Understanding the Operation class The Operation class is in the mx.rpc.soap package. It describes the operation at the web service, and it's an integral part of requesting a web service. It's important to understand the methods, properties, and events of the Operation class.
Properties of the Operation class Table 10-4 shows the most important properties of the Operation class. T a b l e 1 0 - 4 . Properties of the Operation class
346
Property
Data type
Description
arguments
Object
Contains the arguments for the operation.
ignoreWhitespace
Boolean
Determines if whitespace should be ignored when processing a SOAP request or response.
lastResult
Object
Contains the result of the last call to the operation.
multiplePartsFormat
String
Determines the type of the result object where the web service defines multiple parts in the output message.
name
String
The name of the operation.
resultFormat
String
The encoding for the Operation result. The choices are object, e4x, and xml.
AbstractService
Provides access to the service that hosts the operation.
Default value
true
array
object
CONSUMING WEB SERVICES WITH FLEX Most of these properties are self-explanatory, but a few need a little more explanation: • arguments: The arguments property contains the parameters that will be passed to the operation when the application calls the send() method. If the send() method also includes its own parameters, the arguments property is ignored. • multiplePartsFormat: The multiplePartsFormat property has the possible values object and array. The value object indicates that the l a s t R e s u l t will be an Object containing properties that correspond to each output part. A value of array means that l a s t R e s u l t is treated as an array. The output part values are added to the Array in the order they occur in the SOAP response. • resultFormat: The resultFormat property determines how the result from the web service is provided to the application. The default value, object, means that the XML content is placed into an object structure as detailed in the WSDL document. Using e4x means that you can use E4X expressions to access the content. The value xml is used for ActionScript 2.0-type XML documents. The Operation class also has a number of methods.
Methods of the Operation class Table 10-5 shows the main methods of the Operation class. T a b l e 10-5. Methods of the Operation class
Method
Parameters
Description
Returns
cancel()
id: String
Cancels the last request or the request with the id passed to the method. Prevents the r e s u l t or f a u l t method from being dispatched.
AsyncToken
clearResultQ
fireBindingEvent: Boolean
Sets the r e s u l t property to null. Useful when the r e s u l t is a large object that is no longer being used.
AsyncToken
OperationQ
webService: AbstractService, name: S t r i n g
Constructor method. Creates a new Operation.
Nothing
send()
arguments: *
Executes the operation, passing any arguments inside the method with the call.
AsyncToken
These methods are self-explanatory, so let's move on to the events of the Operation class.
347
CHAPTER 10
Events of the Operation class The Operation class dispatches the events summarized in Table 10-6. T a b l e 1 0 - 6 . The events dispatched by the Operation class
Event
Type
Description
fault
FaultEvent
Dispatched when an Operation call fails.
header
HeaderEvent
Dispatched when an Operation call returns with SOAP headers in the response. A HeaderEvent is dispatched for each SOAP header.
result
ResultEvent
Dispatched when an AbstractOperation call returns successfully.
These events work in the same way as for the web service. You set an event listener for each event and respond appropriately when they are dispatched. It's possible to set a listener for a r e s u l t and f a u l t on both the web service and the operation. The f a u l t event of the WebService responds to a web service failure where the fault isn't handled by the operation itself. If the operation handles the fault, it will dispatch its own f a u l t event. Next, let's look at the process that you'll need to follow to consume a web service using ActionScript.
Consuming a web service with ActionScript The steps to consume a web service in ActionScript are much the same as those used in the tag-based approach. 1. Create an instance of the WebService class, providing a valid wsdl property. 2. Optionally, add a [Bindable] metatag if you want to bind the results of the web service to other controls. 3. Specify the web service operation and identify the arguments to send. 4. Call the send() method of the web service operation. 5. Process the response from the web service. Let's work through the process in more detail.
Creating the web service request To start the process, you would create a new WebService object using the constructor method, as shown here: var wsRequest¡WebService = new WebServiceQ; If you want to bind the results from a web service request made with ActionScript, you'll need to use the [Bindable] metatag when you declare the object, as shown here:
348
CONSUMING WEB SERVICES WITH FLEX [Bindable] var wsRequest:WebService = new WebServiceQ; This metatag will allow you to use binding expressions in curly braces for the bound properties of your other components. For example, you might want to bind the l a s t R e s u l t directly to the text property of another component. You can use the loadWSDL() method to specify the URL for the WSDL file, as follows: wsRequest.loadWSDL("urlToTheWSDL"); You can also set the wsdl property first and call the loadWSDL() method without passing the URL as an argument. wsRequest.wsdl = "urlToTheWSDL"; wsRequest. loadWSDLQ;
Specifying the operation It's a little more complicated to specify the operation in ActionScript compared with using the element. You can add an operation by creating an Operation object, as shown here: var wsRequest:WebService = new WebServiceQ; var wsOperation: Operation; wsOperation = wsRequest["operationl\lame"]; An Operation represents the operation on the web service. As you can see, you need to declare an Operation object first, and then associate it with the web services request, passing a S t r i n g for the name of the operation. You can set the arguments at the same time using the arguments property. In this case, each of the arguments for the operation must be created as properties of an Object. You can then set that Object as the arguments property of the web service request, as shown here: var args = new Object Q; args.paraml = "value 1"; args.param2 = "value 2"; wsOperation.arguments = args; It's also possible to pass the arguments inside the call to the send() method, which you'll see in the next example.
Making the request In ActionScript, it's simple to make the request for the web service. You use the name of the web service and call the send() method. wsRequest.send();
349
CHAPTER 10 You can also call the operation directly as a method of the web service, as shown in the next line: wsRequest.wsOperation(); If you use the second approach, you can pass the arguments directly with the operation or with the send() method of the operation. The following two lines are equivalent: wsRequest.wsOperation("value 1", "value 2 " ) ; wsRequest.wsOperation.send("value 1", "value 2 " ) ; Remember that passing arguments directly with the operation overrides any arguments set as the arguments property of the operation.
Receiving the response The addEventListener() method assigns event handlers for the r e s u l t and f a u l t events. You would use the method as shown in the following code block: wsRequest.addEventListener(ResultEvent.RESULT, resultHandler); wsRequest.addEventListener(FaultEvent.FAULT, faultHandler); The r e s u l t event handler function, resultHandler(), receives a ResultEvent as an argument. The f a u l t handler function receives a FaultEvent. You can use properties of each event to access information from the web service. You can also assign event handlers to the operation itself. This process allows you to respond directly to any errors that occur in the operation itself, rather than from the web service. wsOperation.addEvent Listener(ResultEvent.RESULT, resultHandler); wsOperation.addEventListener(FaultEvent.FAULT, faultHandler);
Accessing the reply You access the reply from the web service in much the same way as you do in the tag-based approach. You need to use the l a s t R e s u l t property of the operation. If the operation returns a single value, you can access the response with the t o S t r i n g ( ) method, as you saw earlier. wsRequest.operationName.lastResult.toString(); You can also use the name of the returned parameter to identify which value to display from the response. wsRequest.operationName.lastResult.returnedParam; As I mentioned earlier, it's important to consider the l a s t R e s u l t property of the operation. By default, the web service returns data as a simple tree of ActionScript objects. If you want to override this setting, you can declare the resultFormat of the operation as e4x so you can use E4X expressions. wsOperation.resultFormat = "e4x";
350
CONSUMING WEB SERVICES WITH FLEX It's important to remember that in E4X expressions, the l a s t R e s u l t property is equivalent to the root element, so you wouldn't include it in the path. If you accept the default resultFormat setting, you'll need to include the name of the root element in the path. You'll also need to consider the effect of namespaces if you're using a resultFormat of e4x. As I mentioned earlier, if the web service response contains a default namespace, you'll need to indicate that you wish to use the namespace with the following ActionScript: private namespace webserviceX = " http://www.webserviceX.NET/"; use namespace webserviceX;
Understanding returned data types One advantage of using the WebService class is that many of the built-in data types specified in the WSDL file are converted to ActionScript 3.0 data types. For example, if the WSDL file specifies a return value that uses the data type x s : i n t , Flex recognizes this value as an i n t data type once the content is loaded. This operates differently from a URLLoader object or an HTTPService object, where all element and attribute values are treated as S t r i n g values. Bear in mind, though, that custom data types won't have an equivalent value in ActionScript. Table 10-7 shows how the data types listed in the WSDL document convert to ActionScript 3.0 data types. T a b l e 10-7. ActionScript conversions of SOAP data types
SOAP data type
ActionScript data type
xs:: s t r i n g
String
xs:: i n t
int
xs:: f l o a t
Number
xs:: boolean
Boolean
xs::date
Date
In our example, the returned data type is double, which doesn't have an ActionScript 3.0 equivalent. This means that the value will be treated as a String.
Handling errors As you saw earlier, you can handle errors from both the web service and the operation using a f a u l t event handler function. Both functions receive a FaultEvent as an argument, and you can access the f a u l t property of the event. You can see the f a u l t message using the f a u l t S t r i n g property, as shown here: FaultEvent.fault.faultString;
351
CHAPTER 10 We'll work through the same example that you saw earlier using a scripted approach. This time, we'll use a custom class to handle the web service request.
Working through a scripted example We'll revisit the example from earlier in the chapter using ActionScript. We'll use the same interface but work with a custom class to access the web service. 1. Start by creating an ActionScript class in the project you created earlier using File > New > ActionScript Class. Give the class the name CurrencyConverter and add it to the package xmlUtilities. The file should contain the following code. Don't worry if Flex Builder has used a slightly different arrangement for the opening curly braces. package xmlUtilities { public class CurrencyConverter{ public function CurrencyConverter () { }
}
}
2. Modify the class file to make it bindable by adding a [ B i n d a b l e ] metatag above the class declaration. This metatag makes all public methods of the class file available for use in binding expressions in the application file. [Bindable] public class CurrencyConverter{ 3. If you would like, you can add the following import statements below the package declaration. These statements reference the class files that the application will need to use. They will also be added automatically as you complete the class file. If you choose to skip this step, please double-check that all of the import statements are present when you've finished creating this class. import import import import
4. Add the following private variable declarations underneath the class file declaration. private var ws:WebService; private var wsOperation:String; private var rate¡Number; The ws object refers to the WebService object. The wsOperation variable refers to the operation on the web service. The r a t e Number stores the rate returned by the operation. 5. Now it's time to create the constructor method for the CurrencyConverter class. Modify the constructor as shown here in bold:
352
CONSUMING WEB SERVICES WITH FLEX public function CurrencyConverterQ { ws = new WebService()j ws.wsdl = "http://www.webservicex.com/CurrencyConvertor.asmxFWSDL"j ws.loadWSDLQj ws.addEventListener(ResultEvent.RESULT, *»wsResultHandler)j ws.addEventListener(FaultEvent.FAULT, wsFaultHandler)j
}
The constructor method starts by creating a new WebService object. It then sets the value of the wsdl property to the URL for the web service. It calls the loadWSDL() method of the ws object to load this WSDL file. The method finishes by adding event handlers. It adds a r e s u l t and f a u l t event handler to the web service. You'll add these private methods next. 6. Add the following private methods: private function wsResultHandler(e:ResultEvent):void { rate = e.target[wsOperation],lastResult.toString(); dispatchEvent(new ResultEvent(ResultEvent.RESULT);
}
private function wsFaultHandler(e:FaultEvent):void { dispatchEvent(new FaultEvent(FaultEvent.FAULT, false, true, «-e. fault));
}
The w s R e s u l t H a n d l e r Q method sets the r a t e variable from the returned result. It uses the wsOperation, which you'll set when the application specifies the operation to call. Notice that the code refers to the operation using e . t a r g e t [ w s O p e r a t i o n ] , because it can't use the S t r i n g value wsOperation as part of a path created with dot notation. The method then dispatches a new ResultEvent. The w s F a u l t H a n d l e r ( ) method dispatches a new FaultEvent with default values for arguments and includes the f a u l t object so the user can see the provided f a u l t S t r i n g . 7. The next step in building the class file is to provide a public method that will call the operation on the web service. Add the c a l l W S ( ) method now. public function callWS(operation:String, fromCurr:String, ^»toCurr:String):void { wsOperation = operation; ws[wsOperation].send(fromCurr, toCurr);
}
The method takes the operation name, from-currency value, and to-currency value as arguments. It calls the operation using the send() method and passing the values. Again, you'll notice the use of the expression w s [ o p e r a t i o n ] to deal with the operation name. 8. The class file will provide two public methods to the user to access the rate, depending on whether the user provides an amount to convert. The first public method, g e t R a t e ( ) , will return only the rate. The second method, c o n v e r t ( ) , will accept a value and perform a conversion of this value based on the returned rate. It will return the converted amount. Add these methods now.
353
CHAPTER 10 public function getRate()¡Number { return rate;
}
public function convert(amount:Number):Number { return rate * amount;
That's it for the content in the class file. The complete code follows, so you can check your version against mine. package xmlUtilities { import mx.rpc.soap.WebService; import mx.rpc.soap.Operation; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; [Bindable] public class CurrencyConverter { private var ws:WebService; private var wsOperation:String; private var rate¡Number; public function CurrencyConverterQ { ws = new WebServiceQ; ws.wsdl = "http://www.webservicex.com/
public function callWS(operation:String, fromCurr:String, ^»toCurr:String):void { wsOperation = operation; ws[wsOperation],send(fromCurr, toCurr);
}
public function getRate()¡Number { return rate;
}
public function convert(amount:Number):Number { return rate * amount;
}
private function wsResultHandler(e:ResultEvent):void { rate = e.target[wsOperation],lastResult.toString(); dispatchEvent(new ResultEvent(ResultEvent.RESULT));
}
private function wsFaultHandler(e:FaultEvent):void { dispatchEvent(new FaultEvent(FaultEvent.FAULT, false, true, «-e. fault));
}
354
C O N S U M I N G WEB SERVICES W I T H FLEX
9. N o w y o u need to build an application that will use this class to q u e r y the w e b service. C r e a t e a n e w application file with the n a m e of y o u r choosing. I've called my file C u r r e n c y C o n v e r t e r C l a s s . 1 0 . M o d i f y the file to display the f o l l o w i n g interface: Figure 10-7 shows h o w this interface appears. C u r r e n c y converter From currency
Select From currency
To currency
S e l e c t to c u r r e n c y
x T
|
Am cunt
Converted
amount
F i g u r e 1 0 - 7 . The Currency Converter application interface
It's v e r y similar to the first exercise, e x c e p t t h a t y o u ' v e a d d e d p r o m p t s to the C o m b o B o x c o m p o n e n t s f r o m the beginning.
355
CHAPTER 10
11. Add a creationComplete attribute to the opening element, as shown in bold in the following line:
The i n i t A p p ( ) function will set up the application after the interface has finished creating. 12. Add the following block beneath the opening element. The script block contains the i n i t A p p ( ) function, as well as other ActionScript that you'll need to set up the application.
This < m x : S c r i p t > block starts by importing the classes you'll need to refer to in the application. Again, you can allow Flex to add these classes automatically if you prefer. These classes include the FlexEvent class, needed for the creationComplete handler; the ResultEvent and FaultEvent, needed for the server response; and the CurrencyConverter customer class that you created in the first part of the exercise. The second-to-last import statement references the class for the Alert control, which you'll use to display user messages. The final import statement refers to the class V a l i d a t i o n R e s u l t E v e n t , which you'll need for the you'll add a little later. The code includes the declaration of the new CurrencyConverter object called myCC. It also declares a c u r r e n c y L i s t Array, which you'll use to populate the currencies in the ComboBox controls. The i n i t A p p ( ) function follows. It receives a FlexEvent as an argument and returns nothing. It creates a new CurrencyConverter object. It adds event listeners for the r e s u l t and f a u l t events of the CurrencyConverter object. The code also includes empty function declarations for these two handler functions.
356
CONSUMING WEB SERVICES WITH FLEX
13. The next task is to populate the ComboBox controls with the list of currency values. You need to assign values to the c o u n t r y L i s t A r r a y and then associate this A r r a y as the d a t a P r o v i d e r property for both ComboBox controls. Modify the i n i t A p p ( ) function as shown in bold here: private function initApp(e:FlexEvent):void { myCC = new WebServiceConsumerQ; myCC.addEvent L i s t e n e r ( R e s u l t E v e n t . R E S U L T , r e s u l t H a n d l e r ) ; myCC.addEventListener(FaultEvent.FAULT, f a u l t H a n d l e r ) ; populateCurrenciesQ j
}
The new line calls the p o p u l a t e C u r r e n c i e s Q function, which you'll add next. 14. Add the p o p u l a t e C u r r e n c i e s Q function: private function populateCurrenciesQ:void { c u r r e n c y L i s t . p u s h ( { l a b e l : " A u s t r a l i a d o l l a r s " , data: "AUD"}); c u r r e n c y L i s t . p u s h ( { l a b e l : " B r i t i s h pound", data: "GBP"}); c u r r e n c y L i s t . p u s h ( { l a b e l : "Canadian d o l l a r " , data: "CAD"}); c u r r e n c y L i s t . p u s h ( { l a b e l : "Euro", data: "EUR"}); c u r r e n c y L i s t . p u s h ( { l a b e l : "Singapore d o l l a r " , data: "SGD"}); c u r r e n c y L i s t . p u s h ( { l a b e l : "South A f r i c a n rand", data: " Z A R " } ) ; c u r r e n c y L i s t . p u s h ( { l a b e l : "US d o l l a r " , data: "USD"}); from_cbo.dataProvider = c u r r e n c y L i s t ; to_cbo.dataProvider = currencyList;
} This function adds values to the c u r r e n c y L i s t A r r a y using the push() method. Each line adds an Object to the Array, consisting of a l a b e l and data property. This example uses the same values that were used in the first exercise. Once the A r r a y is populated with the seven currencies, it sets the c u r r e n c y L i s t as the d a t a P r o v i d e r for both the from_cbo and to_cbo controls. If you test the application at this point, you'll see that both controls contain a list of currencies. 15. The application will use a number validator to ensure that the user enters a valid number value into the Amount field. The application will proceed with the conversion only if the entry is valid, so it will use a slightly different process than the one used in the first example. Add the following below the closing < / m x : S c r i p t > tag: This NumberValidator doesn't require an entry in the amountjtxt control. If there is no entry, only the exchange rate will display. Otherwise, the application will calculate the converted amount. When the user makes an entry, if the entry includes nonnumeric characters, the error message Please enter a number displays. Validation occurs when the user clicks the Convert button. If the entry is valid, the application calls the c a l l C C Q method, passing a V a l i d a t i o n R e s u l t E v e n t . If the entry isn't valid, the application clears any entries in the convertedAmountjtxt control.
357
CHAPTER 10
In this version of the application, clicking the Convert button doesn't call the web service. Instead, it calls the validator, which in turn calls the web service if the amountjtxt entry is either blank or a valid number. Figure 10-4, earlier in the chapter, shows the effect of an invalid entry in the Amount field. 16. To respond to the v a l i d event of the , you'll need to add the c a l l C C ( ) function that you referenced earlier. private function callCC(e:ValidationResultEvent):void { var fromCurrencylndex:int = from_cbo.selectedlndex; var toCurrencyIndex:int = to_cbo.selectedlndex; if (fromCurrencyIndex!= -1 && toCurrencylndex != -l) { myCC.callWS("ConversionRate", from_cbo.selectedltem.data, *»to_cbo.selectedItem.data); } else { Alert.show("Select currencies to convert before clicking the ^»Convert button");
}
}
This function creates variables for the selected From currency index (fromCurrencylndex), and the selected To currency index (toCurrencylndex). It tests that the user has selected both currencies by comparing the s e l e c t e d l n d e x properties with the value - l . If an index is selected for both ComboBox components, the function calls the c a l l W S ( ) method of the CurrencyConverter object, passing the operation name and the from-currency and to-currency arguments. If the user hasn't selected currencies, the e l s e block displays an Alert control with the message Select currencies to convert before clicking the Convert button. Figure 10-8 shows the message that the user will see in this case.
F i g u r e 1 0 - 8 . Clicking the Convert button without selecting currency values generates an alert.
17. Once the call to the web service is made, the r e s u l t and f a u l t event handlers need to be configured to deal with the server response. Modify the r e s u l t handler as shown here:
358
CONSUMING WEB SERVICES WITH FLEX private function resultHandler(e:ResultEvent):void{ var amtToConvert:String = amount jtxt.text; if (amtToConvert.length > 0) { convertedAmountjtxt.text = *»String(myCC.convert(Number(amtToConvert)));
The resultHandler() function receives a ResultEvent object as an argument. It starts by identifying the entry in the amount j t x t control. If there is an entry in thisTextlnput component, the application will convert the supplied value into the new currency. It does so by calling the convertQ public method of the CurrencyConverter class, passing the amount cast as a Number. The application assigns this value to the text property of the convertedAmountjtxt control. Notice that it uses the S t r i n g Q constructor to cast the returned calculation as a String. If the user has not entered an amount to convert, the application calls the getRate() method of the CurrencyConverter. It places the call inside the S t r i n g Q constructor method to cast it appropriately before assigning it to the text property of the Textlnput control. 18. The application also needs to respond to a fault in the call to the web service. It will do this by displaying an Alert control with the relevant error message. Modify the f a u l t handler as shown in the following code block: private function faultHandler(e:FaultEvent):void { Alert.show(e.fault.faultString);
}
The faultHandlerQ function receives a FaultEvent as an argument. It displays the f a u l t S t r i n g property of the passed f a u l t object in an Alert control. Figure 10-9 shows the effect of this function.
F i g u r e 1 0 - 9 . The application displays an alert when notified of a fault.
I generated the error by changing the value of the wsdl property to a nonexistent URL. You could assign your own custom error message instead, if you want to display something more descriptive.
359
CHAPTER 10 Well done—you've completed the application file. The complete code follows, so you can check it against your own version: 0) { convertedAmountjtxt.text = *»String(myCC.convert(Number(amtToConvert)));
private function faultHandler(e:FaultEvent):void { Alert.show(e.fault.faultString);
}
360
CONSUMING WEB SERVICES WITH FLEX private function callCC(e:ValidationResultEvent):void { var fromCurrencylndex:int = from_cbo.selectedlndex; var toCurrencyIndex:int = to_cbo.selectedlndex; if (fromCurrencylndex != -1 && toCurrencylndex != -l) { myCC.callWS("ConversionRate", from_cbo.selectedltem.data, *»to_cbo.selectedltem.data);
} else { Alert.show("Select currencies to convert before clicking *»the Convert button");
]]>}
}
Finish
Cancel
F i g u r e 10-11. Selecting the source folder for the generated classes
After clicking Next, you'll be asked for the location of the WSDL file for the web service. Figure 10-12 shows the settings for our example. I've specified that I'll get the details directly from the web service and provided the location of the WSDL file. If you want to use this example outside the Flex environment, the web service will need to have a cross-domain policy file allowing access to the SWF application. Luckily, the web service chosen here provides the policy file. In the final step of the wizard, you need to select the operation and port. The wizard will identify the names of all services and operations available at the web service. In Figure 10-13, you can see that the service CurrencyConvertor is available. In fact, no other services are available at the web service used in the example.
363
CHAPTER 10
I Import Web Service (WSDL) Specify WSDL to Introspect Enter the WEDL URI where the web service description is located.
! e
How will your application access the web service? (*) Directly from the dient (requires ero3sdomain file) Throuqh a LiveCyde Data Services proxy destination How do I use LCDS? Destination: |
F i g u r e 1 0 - 1 2 . Providing the WSDL file location
Unie
I Import Web Service (WSDL) Confiqure Code Generation
Specify the web service operations for which to generate ActionScript dicnt dosses.
! b
Web service operations Service:
CurercyCorvertor
Port: Select the web service operations to import: operations M ConversioriRate(parameters: ConversionRate): CanversionRateRe...
ActiorGcript dient dasses Package: Main dass;
com.webservicex CirrencyConvertor
[
< Back
]
Next >
|
Finish
fj [
Caicd
]
F i g u r e 1 0 - 1 3 . The final step of the Web Service Introspection wizard
364
CONSUMING WEB SERVICES WITH FLEX The wizard also asks for the port to use. This setting equates to the method of connection for the web service. In the case of this web service, you can access the operation in three different ways: by using SOAP, by using an HTTP GET request, or by using an HTTP POST request. In Figure 10-13, you can see that I've left the CurrencyConverterSoap option selected. I've also specified that I want the operation ConversionRate. No other operations are available at this web service. The wizard specifies that it will create the classes in the com.webservicex package and that the main class will be CurrencyConverter. Click Finish to generate the required classes. Figure 10-14 shows all of the classes generated by the wizard. Î f c Flex Navigator
y
O
a
g
.wsdl IB
& bin-debug html -template &
libs
¿••• elements. In our case, you'll see the following elements:
These elements indicate that a SWF application can access the web service using any of these three methods: SOAP, GET, or POST. Let's look at all three methods, starting with GET.
Using GET to consume a web service You can use an HTTP GET request to consume a web service if the service provides that method of access. The GET method sends name/value pairs in the query string, and each pair is separated by an ampersand character. Here is an example of variables sent with a GET request: http://www.myurl.com?varl=valuel&var2=value2&varB=valueB The variable pairs appear after the question mark (?) character at the end of the URL. In the case of a web service, you use the URL for the operation, not the URL of the WSDL file. You can find this address in the following elements: Here, the address is http://www.webservicex.com/CurrencyConvertor.asmx. The following elements describe the location of the web service within this URL:
375
CHAPTER 13 The line in bold indicates the web service is accessed by adding the ConversionRate folder to the web service location. Putting these elements together indicates that the web service is at the following location: http://www.webservicex.com/CurrencyConvertor.asmx/ConversionRate You will need to send any parameters for this web service as part of the URL. The WSDL file indicates that the GET web service needs two parameters, as shown in the following elements: You need to send the FromCurrency and ToCurrency values. These are both S t r i n g types. At the beginning of the WSDL file, you can see a list of acceptable values in the element. If you wanted to convert British pounds to US dollars, you would use the FromCurrency value GBP and the ToCurrency value USD. You would need to append these values to the URL, as shown here: http://www.webservicex.com/CurrencyConvertor.asmx/ConversionRate *»?FromCurrency=GBP&ToCurrency=USD Compared to the alternatives, consuming a web service using the GET method in Flash is by far the easiest of all options shown in this chapter. Even so, you can see from the preceding content that decoding the WSDL file to find the relevant elements is quite a difficult process. I guess you know why Flash designers and developers are unhappy that they don't have access to the WebService class! It's harder to use a POST method to consume a web service or to assemble a SOAP request manually. In a GET request, all you need to do is to modify the URL for the web service to include the parameters to send. In a POST request, you need to assemble the parameters in a URLVariables object and change the method property for the URLLoader object. In a SOAP web service, you must create the XML content for the request yourself. In my experience, this is harder than it looks!
376
CONSUMING WEB SERVICES WITH FLASH
Don't forget that consuming a web service loads data from a source in a different security sandbox from the SWF application. If you want to test the example outside Flash, the web service will need to provide a cross-domain policy file allowing access to the SWF. Where no policy file exists, you'll need to proxy the content locally using a server-side file. You can find out more about Flash Player 10 security in Chapter 5.
Let's work through a simple example of how to consume the currency converter web service using a GET request. As in the previous chapter, this exercise consumes the web service that converts from one currency to another. The user will provide both the from and to currencies, as well as an amount to convert. We'll use the GET method to consume the web service, and the application will use procedural code. If you prefer a class-based approach, feel free to modify the code.
Working through a GET example The WSDL file for this web service is at http://www.webservicex.com/CurrencyConvertor.asmxPWSDL. You can find a full discussion of this WSDL file at the start of Chapter 10. 1. Open the starter file c c S t a r t e r . f l a . Figure 11-1 shows the interface. Currency converter
!
From currency
[
To currency
i
Amount Convert
Converted amount
J n
Figure 11-1. The interface for the currency converter application The application contains two ComboBox components, which the application will populate with a list of currencies. It contains a Textlnput control for the user to enter an amount, as well as a Convert button and Textlnput control in which to display the results. To the right of the components is a dynamic text field for displaying messages to the user. 2. Start by configuring the ComboBox controls, in a function called initAppQ. Add a new layer called actions and enter the following code: import f l . d a t a . D a t a P r o v i d e r ; initAppQ; stop(); function i n i t A p p ( ) : v o i d { populateCurrenciesQ;
}
The code starts by importing the DataProvider class. This is necessary because the application will use a DataProvider object to populate the ComboBox controls. The code then calls the initApp() function and includes a stop() action. The initAppQ function follows. It contains one line: a call to the populateCurrenciesQ function, which will load the ComboBox components. You'll add this function next.
377
CHAPTER 13 3. The following populateCurrencies() function adds currencies to both ComboBox components. Add it now. function populateCurrencies():void { var dp:DataProvider = new D a t a P r o v i d e r Q ; d p . a d d l t e m ( { l a b e l : " C h o o s e . . . " , data: " 0 " } ) ; d p . a d d l t e m ( { l a b e l : " A u s t r a l i a d o l l a r s " , data: "AUD"}); d p . a d d l t e m ( { l a b e l : " B r i t i s h pound", data: " G B P " } ) ; d p . a d d l t e m ( { l a b e l : "Canadian d o l l a r " , data: "CAD"}); d p . a d d l t e m ( { l a b e l : "Euro", data: " E U R " } ) ; d p . a d d l t e m ( { l a b e l : "Singapore d o l l a r " , data: "SGD"}); d p . a d d l t e m ( { l a b e l : "South A f r i c a n rand", data: " Z A R " } ) ; d p . a d d l t e m ( { l a b e l : "US d o l l a r " , data: " U S D " } ) ; from_cbo.dataProvider = dp; to_cbo.dataProvider = dp;
}
This function starts by declaring a new DataProvider object called dp. The next eight lines populate this object using the addltem() method. Each new item is an object containing a l a b e l property and a data property. Except for the first item, all other items use an abbreviation for the data value. This abbreviation is required by the web service. The first item contains a S t r i n g representation of the value 0. I've purposely made this value a S t r i n g to be consistent with the other values. As with the Flex examples in the previous chapter, I've chosen only a small subset of the currencies available to the web service. If you want to see a complete list of all currencies available, open the WSDL file in a web browser and look for the element near the top of the file. Feel free to add other elements if your country is not represented. 4. Test the application now, and you should see the two ComboBox controls populated with a list of currencies. Figure 11-2 shows how the application should appear at this point. Currency c o n v e r t e r From currency To currency Amount
Choose... Choose... Australia dollars
F =
British pound Canadian dollar Euro
Converted amount
Figure 11-2. The application showing a populated ComboBox control The name of the currency displays in the ComboBox. The data property of each value contains the abbreviation for that currency.
378
CONSUMING WEB SERVICES WITH FLASH 5. The next task is to configure the Convert button to call the web service. Add the following event listener function to the initAppQ function: function i n i t A p p ( ) : v o i d { populateCurrencies(); convert_btn.addEventListener(MouseEvent.CLICK,
}
clickHandler);
The addEventListener() method adds the handler function. Clicking the button will call the clickHandler() function, which follows. Add this function to the actions layer. function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedItem.data; var toCurrency:String = to_cbo.selectedItem.data; var convertAmount:String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { if (! isNaN(Number(convertAmount))) { message_txt.text = "Good to go!"
}
else { message_txt.text = "Enter a number to convert";
}
}
else { message_txt.text = "Enter an amount to convert"
}
}
else { message_txt.text = "Select both a 'from' and ' t o ' currency";
}
You'll modify this function shortly. To start with, the function tests entries from the user. The first two lines find the selected data values from the ComboBox components and the amount_ t x t control. The function checks that the user has selected values for both ComboBox controls. It does this by comparing the selectedlndex with 0, which is the selectedlndex of the Choose... item in each ComboBox. If the user hasn't made a selection, the selectedlndex value will be 0. If the user has selected a value, the code checks that there is an entry in the amount_txt Textlnput control. It checks the length of the entry to make sure it is greater than 0. If so, the function checks to see that the value entered in the amount_txt control is numeric. At each stage of the function, a message displays in the message_txt dynamic text field. You'll replace the Good to go! message with the code to call the web service in the next step.
379
CHAPTER 13 6. At this point, test the application to make sure that you're seeing the correct messages. Figure 11-3 shows the message that should appear if the user clicks the Convert button without making any selections. You should also test for a blank or nonnumeric Amount entry. Currency converter From currency
[ Choose...
To currency
1 Choose...
Amount
T
1
Select b o t h a 'from' a n d 'to' currency
H Convert
Converted amount
1
* n
Figure 11-3. The clickHandlerO function displays an error message. 7. Next, you need to modify the clickHandler() function to call the web service. In this example, it will send the values to the web service using GET by modifying the URL. Replace the message_txt.text = "Good to go!" line in the clickHandlerQ function with the following lines, which clear any existing message and call the webServiceGET() function: messagejtxt.text = " " ; webServiceGET(fromCurrency,
toCurrency);
The second line calls the webServiceGET() toCurrency values.
function,
passing the
fromCurrency and
Add the following webServiceGet() function. I'll explain it after the listing. function webServiceGET():void { var requestURL:String = "http://www.webservicex.net/ ^»CurrencyConvertor.asmx/ConversionRate? *»FromCurrency=" + from + "&ToCurrency=" + to; var wsRequest:URLRequest = new URLRequest(requestURL); var webService:URLLoader = new URLLoaderQ; webService.addEventListener(Event.COMPLETE, completeHandler); webService.addEvent Listener(IOErrorEvent.I0_ERR0R, ioErrorHandler); webService.addEvent Listener(HTTPStatusEvent,HTTP_STATUS,
}
^-httpStatusHandler); webService.load(wsRequest); messagejtxt.text = "Contacting web s e r v i c e " ;
The function starts by declaring a S t r i n g variable for the location of the web service. The location is at http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate, as I explained earlier in the chapter. The URL also includes the variables to send. They are listed as name/value pairs after the question mark (?). The function then declares a URLRequest object called wsRequest, passing the requestURL value as an argument. It also declares a URLLoader object called webService.
380
CONSUMING WEB SERVICES WITH FLASH The function adds three event listeners to the webService: one for the complete event; one for the ioerror event, in case there is a problem with the web service; and one for the httpStatus event, following the progress of the call. I've added the third event listener to show you the types of messages that the web service can return. The function calls the load() method of the webService object, passing the URLRequest. It finishes by displaying the message Contacting web service to the user. The function doesn't need to set the method of the URLLoader to GET, because that's the default method. It doesn't need to create a URLVariables object to pass the variables, because the variables are added to the end of the web service URL. 8. Add the event handler functions that follow: function completeHandler(e:Event):void { var returnedXML:XML = new XML(e.target.data); var rate:Number = Number(returnedXML.toString()); var convertedAmount:Number = Number(amount_txt.text) * r a t e ; r e s u l t _ t x t . t e x t = String(convertedAmount);
}
function ioErrorHandler(e:ErrorEvent):void { message_txt.text = "Error contacting web service: " + e . t e x t ;
}
function httpStatusHandler(e:HTTPStatusEvent):void { message_txt.text = "httpStatus " + e . t o S t r i n g Q ;
}
The first function, completeHandler(), processes the web service response and calculates the converted amount. It finds the returned value as an XML object using the expression XML(e.target.data). The web service returns the following XML document structure: 0.8947 The completeHandlerQ function finds the rate by using the t o S t r i n g Q method to return only the numeric portion of the XML document. It casts this value as a Number using NumberQ. It then finds the converted amount by multiplying the user-supplied amount, cast as a Number, by the returned rate. Remember that the application has already dealt with nonnumeric responses before it called the web service. The converted amount is cast as a S t r i n g so the code can assign it to the text property of the r e s u l t j t x t control. The ioErrorHandler() function will respond to errors in the web service. It displays the message Error contacting web service with the text value of the error. You can test this function a little later by using an incorrect URL for the web service. The final function responds when the web service sends back an HTTP status message. This message indicates the status of the request. You've seen these types of messages when a 404 File Not Found error occurs while loading a web page. When the call completes, the status of the request will display in the messagejtxt control. A successful request will provide a status of 200.
381
CHAPTER 13 9. Test the application. Choose two currencies, enter an amount to convert, and click the Convert button. Figure 11-4 shows a sample conversion from British pounds to US dollars. Currency converter From currency To currency
I US dollar
Amount
|l5QG [
Converted amount
„ I
[ British pound
T
-1
I 1
http Status [HTTPStatusEvent L y p e - hLLpSLdLus b u b b l e s - i d l s e cancelable=false eventPhase=2 status=200]
H
Convert
2676.6
? n
Figure 11-4. The completed currency converter application The application displays the HTTP status message to the right. Notice that it includes a status property with a value of 200. The message also indicates the other properties of the HTTPStatusEvent, such as its type and whether it is cancelable. The application also displays the converted amount at the bottom of the screen. Notice that this amount doesn't round to two decimal places, so you may want to fix that yourself. The complete code for this application follows, and you can find it saved in the file c c G e t . f l a with the chapter resources: import f l . d a t a . D a t a P r o v i d e r ; initAppQ; stop(); function i n i t A p p ( ) : v o i d { populateCurrencies(); convert_btn.addEventListener(MouseEvent.CLICK,
}
clickHandler);
function populateCurrencies():void { var dp:DataProvider = new DataProviderQ; dp.addltem({label: "Choose...", data: " 0 " } ) ; dp.addltem({label: "Australia d o l l a r s " , data: "AUD"}); dp.addltem({label: " B r i t i s h pound", data: " G B P " } ) ; dp.addltem({label: "Canadian d o l l a r " , data: "CAD"}); dp.addltem({label: "Euro", data: " E U R " } ) ; dp.addltem({label: "Singapore d o l l a r " , data: "SGD"}); dp.addltem({label: "South African rand", data: "ZAR"}); dp.addltem({label: "US d o l l a r " , data: "USD"}); from_cbo.dataProvider = dp; to_cbo.dataProvider = dp;
}
382
CONSUMING WEB SERVICES WITH FLASH function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedItem.data; var t o C u r r e n c y : S t r i n g = to_cbo.selectedItem.data; var convertAmount:String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { if (! isNaN(Number(convertAmount))) { message_txt.text = " " ; webServiceGET(fromCurrency, toCurrency);
}
else { message_txt.text = "Enter a number to c o n v e r t " ;
}
}
else { message_txt.text = "Enter an amount to convert"
}
}
else { message_txt.text = " S e l e c t both a 'from' and ' t o ' currency";
}
}
function webServiceGET(from:String, t o : S t r i n g ) : v o i d { var requestURL:String = "http://www.webservicex.net/ «CurrencyConvertor.asmx/ConversionRate? «FromCurrency=" + from + "&ToCurrency=" + t o ; var wsRequest:URLRequest = new URLRequest(requestURL); var webService:URLLoader = new URLLoaderQ; webService.addEvent Listener(Event.COMPLETE, completeHandler); webService.addEvent Listener(IOErrorEvent.I0_ERR0R, i o E r r o r H a n d l e r ) ; webService.addEventListener(HTTPStatusEvent.HTTP_STATUS,
}
«httpStatusHandler); webService.load(wsRequest); message_txt.text = "Contacting web s e r v i c e " ;
function completeHandler(e:Event):void { var returnedXML:XML = new X M L ( e . t a r g e t . d a t a ) ; var rate:Number = Number(returnedXML.toString()); var convertedAmount:Number = Number(amount_txt.text) * r a t e ; r e s u l t _ t x t . t e x t = String(convertedAmount);
}
function i o E r r o r H a n d l e r ( e : E r r o r E v e n t ) : v o i d { message_txt.text = " E r r o r contacting web s e r v i c e : " + e . t e x t ;
}
function httpStatusHandler(e:HTTPStatusEvent):void { message_txt.text = "httpStatus " + e . t o S t r i n g Q ;
}
As I mentioned earlier, requesting a web service using GET is probably the simplest approach for consuming web services with Flash. Let's turn our attention to the POST method next.
383
CHAPTER 11
Consuming a web service with POST When you consume a web service using the POST method, you send the variables required for the service using the POST HTTP method. The process is the same as sending a form to the server for processing. In this method, the variables are sent with the page request, rather than being added to the query string, as in the GET method. The next example re-creates the same functionality of the first example using a POST request.
Working through a POST example We'll consume the same web service and send the variables using the POST method. If you work through the WSDL file, you'll see that the example needs to use the same URL as in the previous example, but without the added variable values. The URL for the POST request follows: http://www.webservi.cex. net/CurrencyConvertor .asmx/ConversionRate 1. Open the starter file ccPOSTstart.fla. As this example is similar to the previous one, this starter file already includes some code on the actions layer. In fact, the code is identical to the application at the end of step 5 in the previous example. The code populates the ComboBox controls and adds a c l i c k event handler for the Button control. It also tests that the user has made the required entries. The code at this point follows, and you can refer back to the previous example for a full explanation: import f l . d a t a . D a t a P r o v i d e r ; initAppQ; stop(); function i n i t A p p ( ) : v o i d { populateCurrencies(); convert_btn.addEventListener(MouseEvent.CLICK,
}
clickHandler);
function populateCurrencies():void { var dp:DataProvider = new DataProviderQ; dp.addltem({label: "Choose...", data: " 0 " } ) ; dp.addltem({label: "Australia d o l l a r s " , data: "AUD"}); dp.addltem({label: " B r i t i s h pound", data: " G B P " } ) ; dp.addltem({label: "Canadian d o l l a r " , data: "CAD"}); dp.addltem({label: "Euro", data: " E U R " } ) ; dp.addltem({label: "Singapore d o l l a r " , data: "SGD"}); dp.addltem({label: "South African rand", data: "ZAR"}); dp.addltem({label: "US d o l l a r " , data: "USD"}); from_cbo.dataProvider = dp; to_cbo.dataProvider = dp;
}
function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedltem.data; var toCurrency:String = to_cbo.selectedltem.data; var convertAmount:String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { message_txt.text = "Good to go!"
384
}
CONSUMING WEB SERVICES WITH FLASH else { messagejtxt.text = "Enter an amount to convert"
}
}
else { message_txt.text = "Select both a 'from' and ' t o ' currency";
}
2. Replace the line messagejtxt.text = "Good to go!" in the clickHandler() function with a call to the webServicePOST() function. Add the following lines to the clickHandler(), as shown in bold here: function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedItem.data; var toCurrency:String = to_cbo.selectedItem.data; var convertAmount¡String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { messagejtxt.text = ""; webServicePOST(fromCurrency, toCurrency);
}
else { messagejtxt.text = "Enter an amount to convert"
}
}
else { messagejtxt.text = "Select both a 'from' and ' t o ' currency";
}
The lines clear any existing text from the messagejtxt control and call the webServicePOST() function, passing the conversion currencies. 3. Add the webServicePOSTQ function shown here to the actions layer: function webServicePOST(from:String, t o : S t r i n g ) : v o i d { var requestURL:String = "http://www.webservicex.net/ «CurrencyConvertor.asmx/ConversionRate"; var wsRequest:URLRequest = new URLRequest(requestURL); var webService:URLLoader = new URLLoaderQ; var args:URLVariables= new URLVariablesQ; webService.addEvent Listener(Event.COMPLETE, completeHandler); webService.addEvent Listener(IOErrorEvent.I0_ERR0R, ioErrorHandler); webService.addEventListener(HTTPStatusEvent.HTTP_STATUSj
}
«httpStatusHandler); args.FromCurrency = from; args.ToCurrency = t o ; wsRequest.method = URLRequestMethod.POST; wsRequest.data = args; webService.load(wsRequest); messagejtxt.text = "Contacting web s e r v i c e " ;
CHAPTER 13 The function works in much the same way as the webServiceGETQ function in the previous exercise. It starts by declaring a S t r i n g variable called requestURL for the location of the web service. It then creates URLRequest and URLLoader objects. This time, however, the function also creates a URLVariables object, because the variables aren't included in the URL for the web service. The function adds three event handlers for the complete, ioError, and httpStatus events. You'll add those functions in the next step. The function adds the from and to currencies as properties of the URLVariables object. The object uses the names FromCurrency and ToCurrency. The function also sets the method to URLRequestMethod. POST to POST the variables to the web service. It finishes by adding the args object as the data property of the request and calling the l o a d ( ) method of the webService object. The last line displays the message Contacting web service in the dynamic text field. 4. You now need to add the event handler functions to deal with the response. There are three functions to add: completeHandler(), ioErrorHandler, and httpStatusHandler. These functions follow: function completeHandler(e:Event):void { var returnedXML:XML = new XML(e.target.data); var rate:Number = Number(returnedXML.toString()); var convertedAmount:Number = Number(amount_txt.text) * r a t e ; r e s u l t j t x t . t e x t = String(convertedAmount);
}
function ioErrorHandler(e:ErrorEvent):void { message_txt.text = "Error contacting web service: " + e . t e x t ;
}
function httpStatusHandler(e:HTTPStatusEvent):void { message_txt.text = "httpStatus " + e . t o S t r i n g ( ) ;
}
These functions are exactly the same as those used in the previous example. You can find a complete explanation earlier in the chapter, at step 8 in the GET example. 5. Test the file and enter currencies to convert. You should see the same outcome as shown earlier in Figure 11-4. You can see that this example is a little more complicated than the GET example. The completed code for the application follows, and you can also find it in the resource file ccPOST.fla. import f l . d a t a . D a t a P r o v i d e r ; initApp(); stop(); function i n i t A p p ( ) : v o i d { populateCurrencies(); convert_btn.addEventListener(MouseEvent.CLICK,
}
function populateCurrencies():void { var dp:DataProvider = new DataProviderQ; dp.addltem({label: " C h o o s e . . . d a t a : " 0 " } ) ;
386
clickHandler);
CONSUMING WEB SERVICES WITH FLASH
}
d p . a d d l t e m ( { l a b e l : " A u s t r a l i a d o l l a r s " , data: "AUD"}); d p . a d d l t e m ( { l a b e l : " B r i t i s h pound", data: " G B P " } ) ; d p . a d d l t e m ( { l a b e l : "Canadian d o l l a r " , data: "CAD"}); d p . a d d l t e m ( { l a b e l : "Euro", data: " E U R " } ) ; d p . a d d l t e m ( { l a b e l : "Singapore d o l l a r " , data: "SGD"}); d p . a d d l t e m ( { l a b e l : "South A f r i c a n rand", data: " Z A R " } ) ; d p . a d d l t e m ( { l a b e l : "US d o l l a r " , data: " U S D " } ) ; from_cbo.dataProvider = dp; to_cbo.dataProvider = dp;
function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedItem.data; var t o C u r r e n c y : S t r i n g = to_cbo.selectedItem.data; var convertAmount:String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { if (! isNaN(Number(convertAmount))) { message_txt.text = " " ; webServicePOST(fromCurrency, toCurrency);
}
else { message_txt.text = "Enter a number to c o n v e r t " ;
}
}
else { message_txt.text = "Enter an amount to convert"
}
}
else { message_txt.text = " S e l e c t both a 'from' and ' t o ' currency";
}
}
function webServicePOST(from:String, t o : S t r i n g ) : v o i d { var requestURL:String = "http://www.webservicex.net/ ^»CurrencyConvertor.asmx/ConversionRate"; var wsRequest:URLRequest = new URLRequest(requestURL); var webService:URLLoader = new URLLoaderQ; var args:URLVariables= new U R L V a r i a b l e s Q ; webService.addEvent Listener(Event.COMPLETE, completeHandler); webService.addEvent Listener(IOErrorEvent.IO_ERROR, i o E r r o r H a n d l e r ) ; webService.addEventListener(HTTPStatusEvent.HTTP_STATUS, ^»httpStatusHandler); args.FromCurrency = from; args.ToCurrency = t o ; wsRequest.method = URLRequestMethod.POST; wsRequest.data = args; webService.load(wsRequest); message_txt.text = "Contacting web s e r v i c e " ;
}
387
CHAPTER 13 function completeHandler(e:Event):void { var returnedXML:XML = new X M L ( e . t a r g e t . d a t a ) ; var rate:Number = Number(returnedXML.toString()); var convertedAmount:Number = Number(amount_txt.text) * r a t e ; r e s u l t _ t x t . t e x t = String(convertedAmount);
}
function i o E r r o r H a n d l e r ( e : E r r o r E v e n t ) : v o i d { message_txt.text = " E r r o r contacting web s e r v i c e : " + e . t e x t ;
}
function httpStatusHandler(e:HTTPStatusEvent):void { message_txt.text = "httpStatus " + e . t o S t r i n g ( ) ;
}
In this example, you saw how to consume a web service using POST. The example is quite similar to the previous GET example, except that you pass the variables in a different way. The final method of consuming a web service is by using a SOAP request.
Consuming a SOAP web service with the as3webservice extension It would be possible to re-create the previous example using a SOAP request; however, the process of assembling the SOAP request correctly is quite a challenge. It involves creating the correct XML document and adding headers; it can be quite a convoluted process. Working through such an example is beyond the scope of an introductory book. As an alternative, I'll introduce you to an open source extension that can consume a web service for you. Instead of assembling the request manually, you need to provide only the URL, and the class library will generate the correct SOAP request and process the response. The asBwebservice extension is an open source web service class library for Flash. It uses ActionScript 3.0 to mimic the functionality of the WebService class available to Flex applications. You need to provide the URL for the web service's WSDL file, as well as the operation name and arguments. The class library builds the SOAP request and handles the response. Without getting into too much detail, the library consists of the following three classes: • The WebService class handles the call to the web service, including the creation of the SOAP request. • The Operation class manages the operation at the web service. • The OperationEvent is a custom event class dispatched by the Operation class. You'll need all three classes to create an application that consumes a web service. From experience, I can tell you that the extension is easy to use and greatly simplifies the process of consuming web services from Flash. Details of the class library are at http://www.wellconsidered. be/blog/as3-webservice-component/. You can download the extension from Google Code at h t t p : / / code.google.com/p/as3webservice/. Both web sites include sample code for using the extension.
388
CONSUMING WEB SERVICES WITH FLASH f
^ A word of caution here: we'll use the asBwebservice class library in the following example; however, this class library is a work in progress. The author of the extension explained that the library isn't complete, and he adds functionality as people request it.
v
At the time of writing, it was possible to use the extension to consume the currency conversion web service for this example. You may get different results with other web services. If you find that to be the case, I encourage you to contact the author of the classes, Pieter Michels, via his web site.
Let's see how to use the as3webservice class library in an example.
Working through an as3webservice example In this example, we'll consume the same web service that you've seen throughout the chapter using the as3webservice class library. 1. The first step is to download and install the asBwebservice extension from the Google Code site. Switch to the Source tab, click Browse, and save the file as3webservice.mxp from the trunk > build folder. 2. You'll need the Adobe Extension Manager to install the extension. If you don't have the Extension Manager, download it from http://www.adobe.com/exchange/em_download/ and install the software. 3. Make sure Flash is not open and double-click the extension to install it to Flash. Once the installation is complete, open the c c S o a p S t a r t . f l a resource file in Flash. Check the Components panel. You should see the wellconsidered components, as shown in Figure 11-5. If you can't see them, you may need to refresh the panel. Click the panel menu on the right side and choose Reload, as shown in Figure 11-5. 4. Before you can consume the web service, you'll need to drag a copy of all three wellconsidered classes—Operation, OperationEvent, and WebService—to the Library panel. Figure 11-6 shows the Library panel for the ccSoapStart.fla file. As I pointed out earlier, you'll need all three classes in order to consume a web service. 5. This example is similar to the previous two examples, so the starter file already includes some code on the actions layer. The code brings the application to the same stage as at the end of step 5 in the first example, so we'll pick up from that point. Modify the message_txt.text = "Good to go!" line in the clickHandler() function as shown in bold in the following code block:
, 1 Components x | El S
&i
El
AIR ServiceMonitoi User Interface
- X Reload Help
Video
•
wellconsidered &
Operation OperationEvent
&
WebService
Figure 11-5. The Components panel showing the wellconsidered classes
Name Q
Button
p ] 3 ComboBox
• j t
Component Assets
MList C$1 Operation OperationEvent Ep
Textlnput
t g i WebService
Figure 11-6. The Library panel showing the wellconsidered classes
389
CHAPTER 13 function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedltem.data; var toCurrency:String = to_cbo.selectedltem.data; var convertAmount:String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { if (! isNaN(Number(convertAmount))) { message_txt.text = ""; webServiceSOAP(fromCurrency, toCurrency)j
}
else { message_txt.text = "Enter a number to convert";
}
}
else { message_txt.text = "Enter an amount to convert"
}
}
else { message_txt.text = "Select both a 'from' and ' t o ' currency";
}
The new lines clear any message showing in the message_txt control and call the webServiceSOAP() function, which you'll add shortly. 6. The application needs to include import statements for the three wellconsidered classes contained in the extension. Add the following lines at the top of the actions layer: import import import
You can find the names of these classes from the Library panel. 7. Add the webServiceSOAPQ function that follows: function webServiceSOAP(from:String, t o : S t r i n g ) : v o i d { var wsdlURL:String = "http://www.webservicex.net/ ^»CurrencyConvertor.asmx?WSDL"; var ws = new be.wellconsidered.services.WebService(wsdlURL); var op:Operation = new Operation(ws); op.addEventListener(OperationEvent.COMPLETE, completeHandler); op.addEvent Listener(OperationEvent.FAILED, failedHandler); op.ConversionRate(from, t o ) ; message_txt.text = "Contacting web s e r v i c e " ;
}
This function creates a new instance of the wellconsidered WebService class, passing the URL for the WSDL file as an argument. Notice that I used the fully qualified name of the WebService class to avoid any name confusion with existing classes. The function also creates a new Operation for the class called op.
390
CONSUMING WEB SERVICES WITH FLASH The webServiceS0AP() function adds two event listeners: one for the complete event and one for the f a i l e d event. These events are both part of the OperationEvent class. The function calls the operation ConversionRate, passing the from and to currencies as arguments. It finishes by displaying a message to the user that says Contacting web service. 8. The last step is to add the two handler functions that follow: function completeHandler(e:OperationEvent):void { var returnedXML:XML = new XML(e.data); var rate:Number = Number(returnedXML.toString()); var convertedAmount:Number = Number(amount_txt.text) * r a t e ; r e s u l t j t x t . t e x t = String(convertedAmount);
}
function failedHandler(e:OperationEvent):void { messagejtxt.text = "Error contacting web service : " + e . d a t a . t e x t ;
}
These functions work in much the same way as you saw in the previous examples. The completeHandlerQ function receives an OperationEvent object as an argument and locates the returned XML document using the expression e.data. It determines the exchange rate by using the t o S t r i n g ( ) method of the XML object. The function uses the same calculations that you saw previously. Please refer back to the first example if you need an explanation. The failedHandler() function responds in the case of an error. It also receives an OperationEvent as an argument. The function displays an error message to the user saying Error contacting web service:, along with the text associated with the error. 9. Test the application. You should be able to select two currencies, enter an amount to convert, click the Convert button, and see the converted amount. You can see an example of how the application should look in Figure 11-4, earlier in the chapter. You can find this application saved in the file ccSoap.fla with your chapter resources. The complete code follows, in case you want to check your work: import be.wellconsidered.services.WebService; import be.wellconsidered.services.Operation; import be.wellconsidered.services.events.OperationEvent; import f l . d a t a . D a t a P r o v i d e r ; initAppQ; stop(); function i n i t A p p ( ) : v o i d { populateCurrencies(); convert_btn.addEventListener(MouseEvent.CLICK, clickHandler);
}
function populateCurrenciesQ:void { var dp:DataProvider = new DataProviderQ; dp.addltem({label: "Choose...", data: " 0 " } ) ; dp.addltem({label: "Australia d o l l a r s " , data: "AUD"}); dp.addltem({label: " B r i t i s h pound", data: " G B P " } ) ; dp.addltem({label: "Canadian d o l l a r " , data: "CAD"});
391
CHAPTER 12
}
d p . a d d l t e m ( { l a b e l : "Euro", data: " E U R " } ) ; d p . a d d l t e m ( { l a b e l : "Singapore d o l l a r " , data: "SGD"}); d p . a d d l t e m ( { l a b e l : "South A f r i c a n rand", data: " Z A R " } ) ; d p . a d d l t e m ( { l a b e l : "US d o l l a r " , data: " U S D " } ) ; from_cbo.dataProvider = dp; to_cbo.dataProvider = dp;
function clickHandler(e:MouseEvent):void { var fromCurrency:String = from_cbo.selectedltem.data; var t o C u r r e n c y : S t r i n g = to_cbo.selectedltem.data; var convertAmount:String = amount_txt.text; if (from_cbo.selectedlndex > 0 && to_cbo.selectedlndex > 0) { if (convertAmount.length > 0) { if (! isNaN(Number(convertAmount))) { message_txt.text = " " ; webServiceSOAP(fromCurrency, toCurrency);
}
else { message_txt.text = "Enter a number to c o n v e r t " ;
}
}
else { message_txt.text = "Enter an amount to convert"
}
}
else { message_txt.text = " S e l e c t both a 'from' and ' t o ' currency";
}
}
function webServiceSOAP(from:String, t o : S t r i n g ) : v o i d { var wsdlURL:String = "http://www.webservicex.net/ —CurrencyConvertor.asmx?WSDL"; var ws = new be.wellconsidered.services.WebService(wsdlURL); var op:Operation = new Operation(ws); op.addEventListener(OperationEvent.COMPLETE, completeHandler); op.addEventListener(OperationEvent.FAILED, f a i l e d H a n d l e r ) ; op.ConversionRate(from); message_txt.text = "Contacting web s e r v i c e " ;
}
function completeHandler(e:OperationEvent):void { var returnedXML:XML = new XML(e.data); var rate:Number = Number(returnedXML.toString()); var convertedAmount:Number = Number(amount_txt.text) * r a t e ; r e s u l t _ t x t . t e x t = String(convertedAmount);
}
function f a i l e d H a n d l e r ( e : O p e r a t i o n E v e n t ) : v o i d { message_txt.text = " E r r o r contacting web s e r v i c e : " + e . d a t a . t e x t ;
}
392
CONSUMING WEB SERVICES WITH FLASH So far in the book, you've seen some ActionScript 3.0 approaches to consuming a web service. Unfortunately, Flash doesn't have access to the same functionality that is available in Flex in the WebService class, so consuming a web service is a much more cumbersome process. There is an ActionScript 2.0 alternative that uses a data component. As with other Flash components, this one can help to simplify the process of building SWF applications. You can drag the component onto the stage and configure it using panel settings instead of by writing ActionScript. I'll cover this topic next.
Consuming a SOAP web service with the WebServiceConnector component Flash can work with SOAP requests using the ActionScript 2.0 WebServiceConnector data component. You need to provide the URL to the WSDL file, and the component will generate the SOAP request for you. You can also script the WebServiceConnector class, but that's beyond the scope of this book. In order to work with the WebServiceConnector component, you must create an ActionScript 2.0 Flash file. Each WebServiceConnector component can work with only one operation at the web service, but you can call the same operation more than once. You need to add extra WebServiceConnector components if you're calling more than one operation in your application. You can find the WebServiceConnector in the Data section of the Components panel of an ActionScript 2.0 document, as shown in Figure 11-7. As with the other ActionScript 2.0 data components, the WebServiceConnector component has no visual appearance in a compiled Flash movie.
Configuring the WebServiceConnector
I Components X \ El
/C=
Adobe Captivate Components
• Aj
Data ( ¡ C j DataHolder DataSet • C j ^ RDBMSResolver WebServiceConnector
,
kî
iîi ] Connects to a WebService Methoc MjpdateKesolver ffl ffl
Media User
ffl i j S
Interface
Video
Figure 11-7. The Data section of the Components panel in an ActionScript 2.0 document
You configure the WebServiceConnector component using the Component Inspector panel. When you select the component in the application, you'll see three tabs in the Component Inspector: • The Parameters tab configures the component and includes the location of the WSDL file and operation name. • The Bindings tab indicates how user interface components interact with the WebServiceConnector. • The Schema tab shows the structure for operation arguments and returned values. We'll start by covering the Parameters tab.
393
CHAPTER 13
Adding parameters To begin, you must enter the settings for the web service in the Parameters tab of the Component Inspector panel. Figure 11-8 shows the panel.
1 Component Component Inspector x | Trigger Data Source. This brings up the Trigger Data Source dialog box, as shown in Figure 11-14. Remember that we're working with an ActionScript 2.0 component here; the Trigger Data Source option won't appear in ActionScript 3.0 documents. You'll need to select the WebServiceConnector component to trigger. You can insert a reference to this component with either a relative or an absolute path, by selecting the corresponding radio button in the Trigger Data Source dialog box. I normally choose the default setting of Relative to create a relative path, in case I need to rearrange my movie timelines later. In the case of a Button instance, Flash adds the following code, assuming WSCInstanceName is the name you gave to the component:
Trigger Data Source Select Data Source component: this._parerrt.cc_ws Sgl
on ( c l i c k ) { // Trigger Data Source Behavior // Macromedia 2003 this._parent.WSCInstanceName.trigger();
}
As I mentioned, you could also type this ActionScript yourself, as you'll see in the final exercise in this chapter.
Binding the results Once you've triggered the component, you'll need to bind the results of your web service to one or more components to display the results. Make sure the WebServiceConnector component is selected and click the Add binding button in the Bindings tab of the Component Inspector.
397
CHAPTER 13 Select one of the results elements. These elements are the values returned by the web service. Figure 11-15 shows the selection of a Number element, which is the only result returned from the web service.
Figure 11-15. Adding a binding for the results You'll need to set the direction for this binding to out, as the value comes out of the WebServiceConnector. You must also select a component, as shown in Figure 11-16.
Bound To
m
Component path: B
Schema location: t e x t : String
Scene 1 WebServiceConnector, Other Panels > Web Services. Figure 11-17 shows the Web Services panel displaying details of the ConversionRate operation.
I Web Services x \
d o E
^ CurrencyConvertor • Q= ConversionRateQ - Get conversion rate from one currency to another currency Differe B - " params FromCurrency : String (string) ToCurrency : String (string) - results : Number (double) - i
W J
H
Figure 11-17. The Web Services panel
399
CHAPTER 13 You can expand each web service listed in the panel to see a list of available operations, as well as the schema for the params and results. This dialog box provides an alternative to viewing the Component Inspector. You can refresh all web services within the panel by clicking the Refresh Web Services button at the top of the panel. You might need to do this if you've entered a WSDL URL but can't see any operations listed in the Parameters tab of the Component Inspector. You can also manage the web services in the list by clicking the Define Web Services button at the top left of the panel. This button has an icon that looks like a globe. Clicking the button displays the Define Web Services dialog box, as shown in Figure 11-18. Define Web Services
m
Add and remove web service urls
Figure 11-18. The Define Web Services dialog box You can use the plus and minus buttons at the top of the dialog box to add and remove the URLs for the WSDL documents for each web service. Click OK to return to the Web Services panel. You can also use the Web Services panel menu to carry out other tasks. For example, Figure 11-19 shows how to view the WSDL document in a web browser
—
, . I Web Services x | O O E ^
- X
Define Web Services... Refresh Web Services
CurrencyConvertor
• O ConversionRateO - Get conversion rate from one currency to another currency Differe ••—params
Figure 11-19. Viewing a WSDL file in a web browser The WebServiceConnector component will become clearer when we work through a simple example. In the next exercise, we'll re-create the currency converter example that you saw earlier.
400
CONSUMING WEB SERVICES WITH FLASH
Working through a WebServiceConnector example In this exercise, we'll use the WebServiceConnector component to consume the currency conversion service we've been working with in this chapter. The WSDL file for this web service is at http://www. webservicex.com/CurrencyConvertor,asmx?WSDL. 1. Open the starter file ccStarterComponent.fla in Flash. This is an ActionScript 2.0 document. The interface is identical to the one in the previous examples, and you can see it in Figure 11-1 earlier in the chapter. 2. Drag a WebServiceConnector component to the application. You can place it anywhere you like, as it does not have a visual appearance. Give the component the instance name cc_ws. 3. Select the WebServiceConnector component and open the Component Inspector panel. In the WSDLURL field of the Parameters tab, enter the URL for the WSDL: http://www.webservicex. com/CurrencyConvertor.asmx?WSDL. Wait for the operation field to populate, and then select the ConversionRate operation. 4. The next task is to populate the ComboBox components with currencies. Add a new layer called actions and enter the following ActionScript 2.0 code: initAppQ; stopQ; function i n i t A p p ( ) : V o i d { populateCurrenciesQ;
}
function populateCurrenciesQ:Void { var dp:Array= new A r r a y Q ; dp.addltem({label "Choose...", data: " 0 " } ) ; dp.addltem({label "Australia d o l l a r s " , data: "AUD"}); dp.addltem({label " B r i t i s h pound", data: " G B P " } ) ; dp.addltem({label "Canadian d o l l a r " , data: "CAD"}); dp.addltem({label "Euro", data: " E U R " } ) ; dp.addltem({label "Singapore d o l l a r " , data: "SGD"}); dp.addltem({label "South African rand", data: "ZAR"}); dp.addltem({label "US d o l l a r " , data: "USD"}); from_cbo.dataProvider = dp; to_cbo.dataProvider = dp;
}
The code starts by calling the initAppQ function and includes a stopQ action. The initAppQ function has a single line, which calls the populateCurrenciesQ function. Notice that, because the code is in ActionScript 2.0, it's necessary to capitalize the first letter of the return type Void. The populateCurrenciesQ function is almost the same as the one used earlier, except that it uses an Array to populate the ComboBox controls. There is no DataProvider class in ActionScript 2.0.
401
CHAPTER 13 5. Test the application now. You should see the two ComboBox controls populated with a list of currencies, as shown in Figure 11-20. Currency converter From currency To currency
Choose... Chl^se... Australia dollars
Amount
British pound Canadian dollar
J
Euro Converted amount
Figure 11-20. Populating the ComboBox components in the application 6. The application will trigger the WebServiceConnector when the user clicks the Convert button. You'll need to add a function that responds to the c l i c k event of the button. Add the following line to the initAppQ function: convert_btn.onRelease = clickHandler; Notice that the code uses ActionScript 2.0 notation to reference the clickHandlerQ function with onRelease. 7. Add the clickHandlerQ function that follows to the actions layer: function clickHandlerQ :Void { if (from_cbo.selectedlndex 0 && to_cbo.selectedlndex 0) { if (amount_txt.text.length > 0) { if (! isNaN(amount_txt.text)) { message_txt.text = "Contacting web s e r v i c e " ; cc_ws.triggerQ;
}
else { message_txt.text = "Enter a v a l i d number to convert";
}
}
else { message_txt.text = "Enter an amount to convert";
}
}
else { message_txt.text = " Select both a 'from' and ' t o ' currency";
}
The function tests that both currencies have been selected. If so, it then tests that the user has entered an amount to convert. The next if statement checks that the entry is numeric. If all entries are present and correct, the function calls the t r i g g e r Q method of the WebServiceConnector. If there are missing or incorrect entries, the function displays an appropriate error message.
402
CONSUMING WEB SERVICES WITH FLASH 8. Now you need to bind the values from the ComboBox components to the params of the web service. Select the WebServiceConnector component and display the Bindings tab of the Component Inspector. Click the Add binding button and select FromCurrency : String. Click OK. Make sure that the binding direction is set to in. Click in the bound to Value column, and click the magnifying glass icon. Choose the from_cbo control and select the value: String property. Click OK. Repeat this process for the to_cbo ComboBox. Figure 11-21 shows how the Component Inspector Bindings tab should appear at this point. X
| Component} Component Inspector x | 0) {
404
CONSUMING WEB SERVICES WITH FLASH if
}
( ! isNaN(amount_txt.text)) { message_txt.text = "Contacting web s e r v i c e " ; cc_ws.trigger();
else { message_txt.text = "Enter a v a l i d number to c o n v e r t " ;
}}
else { message_txt.text = "Enter an amount to c o n v e r t " ;
}
}
else { message_txt.text = " S e l e c t both a 'from' and ' t o ' currency";
}
}
function r e s u l t H a n d l e r ( e : O b j e c t ) : V o i d { var amtToConvert:Number = Number(amount_txt.text); var rate:Number = N u m b e r ( e . t a r g e t . r e s u l t s ) ; message_txt.text = " " ; r e s u l t _ t x t . t e x t = String(amtToConvert*rate);
}
In this exercise, we created a simple currency converter application using the WebServiceConnector component. The WebServiceConnector component created the SOAP request and sent through currency values from two ComboBox controls. The Flash application received a conversion value, which it used to calculate a converted amount. As you can see, using the WebServiceConnector component to consume a web service in Flash is significantly easier than the ActionScript 3.0 approaches. You can see why Flash designers and developers are disappointed that they don't have access to the ActionScript 3.0 WebService class that is available to Flex applications.
Summary In this chapter, I showed you different ways that you can consume a web service in Flash. Unfortunately, Flash doesn't have access to the WebService class available to Flex applications. Instead, you can choose from a variety of different approaches, and I showed you how to consume a currency conversion web service using an HTTP GET and POST. I also showed you an open source extension that provides functionality for consuming a SOAP web service. In the second half of the chapter, we looked at how to work with the WebServiceConnector data component. You saw how to use this ActionScript 2.0 component to consume the same currency conversion web service. In the final two chapters of the book, we'll work through case studies showing how to combine what you've learned so far into real-world examples. The next chapter will cover a Flash case study where we'll consume the Flickr web service. In the final chapter, we'll consume the kuler RSS feed from the Adobe web site using Flex.
405
Chapter 12
FLASH CASE STUDY The final two chapters of this book take you through case studies so you can apply what you've learned throughout the book in real-world applications. In this chapter, we'll build an application that queries Flickr and displays the photos that it finds. We'll use the Flickr web service to locate and display photographs and related information. As you probably know, Flickr provides access for people to store their photos. It also allows users to view these photos and find out more about the photographer. You can access the images at Flickr in a number of different ways: by searching using a keyword, by viewing photos marked as interesting, and by seeing the most recent photos uploaded. The application that we'll build will provide all of this functionality for locating images in Flickr. It will search by keyword, display recently uploaded images, and display interesting photos. You'll be able to view a collection of thumbnails from each search and click to see a large version of each one. You'll also be able to view the image at the Flickr web site and find out more about the photographer. In this chapter, we'll use Flash to build the Flickr application. We'll use a function-based approach for the ActionScript code, rather than working with custom classes. In the next chapter, we'll create an application using custom classes in Flex. As usual, all of the resources for this chapter are available for download at h t t p : / / www. -Friendsofed. com. Before working through the example, you should have an idea of how Flickr works and the ways in which developers can access Flickr content.
407
CHAPTER 13
Understanding Flickr Flickr is a web site that hosts images and videos. It creates an online community that allows photographers to share their work with the rest of the world. It allows photographers to provide details about themselves and their images. The web site organizes images using tags to provide topic information. Visitors to the site can search for images by keyword or browse through collections of images. Figure 12-1 shows an example of a search for the word sunset. flickr: iifwrch - Mfizilln firpfox Rfc-
Virw Mrixl i rtffViirii • Musi imrmil • Umt inEiarediiij
S u n s e t B e a c h Dyxengravity (=1206 comments
if 347 faves
\J 7
notes Taoocd with sunset, Dcocn. nawafl, maul.. uploaded March 11,2007 Taken in Suiisvl Beach Hawaii (map) see more ofxengrawtys pnatos, or visit his
StiuniuiwJ Result* Sunset I'ainiinqs We I la*e Millions of Produds. Ounset Paintings. www.Cslibex.com Vistinq Ritnsftl Ml"? Your Official Travel Site Hotel Deal* from 1M+ Sites www kayak mm
profile
f r e e Why We Need a Weekly Sabbath Rest Get free book about God's Sabbath and why we need a weekly rest www.gnmagazine.orgteabbatn
Graslei after sunset, Gent,
O! SEARCH ¡sunset
B e l g i u m DyErroba Transferrffia data frocn fnrmL3tatK,Äd