Programmer to Programmer™
Get more out of WROX.com Interact
Chapters on Demand
Take an active role online by participating in our P2P forums
Purchase individual book chapters in pdf format
Wrox Online Library
Join the Community
Hundreds of our books are available online through Books24x7.com
Sign up for our free monthly newsletter at newsletter.wrox.com
Wrox Blox
Browse
Download short informational pieces and code to keep you up to date and out of trouble!
Ready for more Wrox? We have books and e-books available on .NET, SQL Server, Java, XML, Visual Basic, C#/ C++, and much more!
Contact Us. We always like to get feedback from our readers. Have a book idea? Need community support? Let us know by e-mailing
[email protected]
Professional Cairngorm™ Introduction ............................................................................................... xxiii
Chapter 1: Introducing Cairngorm ...................................................................1 Chapter 2: Frameworks and Design Patterns .................................................13 Chapter 3: The ServiceLocator ......................................................................29 Chapter 4: The ModelLocator ........................................................................45 Chapter 5: The FrontController ......................................................................55 Chapter 6: Events .........................................................................................63 Chapter 7: Commands ..................................................................................73 Chapter 8: Delegates ....................................................................................81 Chapter 9: Value Objects ..............................................................................89 Chapter 10: How the Pieces Work Together ...................................................97 Chapter 11: Project Overview .....................................................................105 Chapter 12: Flex Project Setup .................................................................. 111 Chapter 13: The Backend .......................................................................... 119 Chapter 14: Main Application Setup ........................................................... 123 Chapter 15: User Registration.................................................................... 129 Chapter 16: User Login .............................................................................. 147 Chapter 17: Adding Posts .......................................................................... 159 Chapter 18: Loading Posts ......................................................................... 173 Chapter 19: Adding a Commenting System ................................................. 185 Chapter 20: Adding Search Capabilities ..................................................... 201 Chapter 21: Reviewing Version One ............................................................ 217 Chapter 22: Combining Classes ................................................................. 223 Chapter 23: Calling Methods on Views ....................................................... 249 Continues
Chapter 24: Sequencing Commands ........................................................... 259 Chapter 25: Criticisms of Cairngorm .......................................................... 263 Chapter 26: Best Practices........................................................................ 271 Chapter 27: Cairngorm Plug-in ................................................................... 279 Chapter 28: Cairngorm Extensions ............................................................. 301 Chapter 29: Looking Back and Ahead ......................................................... 309 Index .........................................................................................................317
Professional
Cairngorm™
Professional
Cairngorm™ Jeremy Wischusen
Wiley Publishing, Inc.
Professional Cairngorm™ Published by Wiley Publishing, Inc. 10475 Crosspoint Boulevard Indianapolis, IN 46256 www.wiley.com Copyright © 2010 by Wiley Publishing, Inc., Indianapolis, Indiana ISBN: 978-0-470-49726-5 Manufactured in the United States of America 10 9 8 7 6 5 4 3 2 1 No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Permissions Department, John Wiley & Sons, Inc., 111 River Street, Hoboken, NJ 07030, (201) 748-6011, fax (201) 748-6008, or online at http://www.wiley.com/go/permissions. Limit of Liability/Disclaimer of Warranty: The publisher and the author make no representations or warranties with respect to the accuracy or completeness of the contents of this work and specifically disclaim all warranties, including without limitation warranties of fitness for a particular purpose. No warranty may be created or extended by sales or promotional materials. The advice and strategies contained herein may not be suitable for every situation. This work is sold with the understanding that the publisher is not engaged in rendering legal, accounting, or other professional services. If professional assistance is required, the services of a competent professional person should be sought. Neither the publisher nor the author shall be liable for damages arising herefrom. The fact that an organization or Web site is referred to in this work as a citation and/or a potential source of further information does not mean that the author or the publisher endorses the information the organization or Web site may provide or recommendations it may make. Further, readers should be aware that Internet Web sites listed in this work may have changed or disappeared between when this work was written and when it is read. For general information on our other products and services please contact our Customer Care Department within the United States at (877) 762-2974, outside the United States at (317) 572-3993 or fax (317) 572-4002. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books. Library of Congress Control Number: 2009933746 Trademarks: Wiley, the Wiley logo, Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in the United States and other countries, and may not be used without written permission. Cairngorm is a trademark of Adobe Systems, Incorporated in the United States and/or other countries. All other trademarks are the property of their respective owners. Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this book. The CairngormStore 2.1 example, FStop application, and Cairngorm plug-in are used according to following terms: Copyright © 2007. Adobe Systems Incorporated. The Cairngorm Extensions are used according to the following conditions: Copyright © 2008, Universal Mind Author: Thomas Burleson, Principal Architect
[email protected], Darron Schall, Principal Architect. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Adobe Systems Incorporated nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
To my son Kai, wife Floe, and the rest of my family who were incredibly understanding and supportive while I was writing this book
About the Author Jeremy Wischusen has over 12 years’ experience designing websites and applications for such clients as myYearbook.com and HBO. In the past, he taught web design for clients such as Wyeth Pharmaceuticals and the Vanguard Group and is currently an instructor at the University of the Arts Department of Continuing Education in Philadelphia, teaching web design and ActionScript. Jeremy is also an active member of the Flashkit online Flash community where he has composed several tutorials on Object Oriented ActionScript. Currently Jeremy works for GoAmerica as a Flex/Flash PHP developer.
About the Technical Editor Jeffry Houser is a technical entrepreneur who likes to share cool stuff with other people. He is the brains behind Flextras, a set of interface Flex Components, and producer of “The Flex Show,” a podcast about Adobe Flex. Jeffry has a computer science degree from the days before business met the Internet. In 1999, he started DotComIt, an Adobe Solutions Partner specializing in rich Internet applications with Flex and ColdFusion. Jeffry is an Adobe Community Expert and has spoken at events all over the U.S. while authoring three technical books, and over 30 articles.
Credits Acquisitions Editor
Vice President and Executive Group Publisher
Scott Meyers
Richard Swadley
Project Editor
Vice President and Executive Publisher
William Bridges
Barry Pruett
Technical Editor
Associate Publisher
Jeffry Houser
Jim Minatel
Production Editor
Project Coordinator, Cover
Rebecca Anderson
Lynsey Stanford
Copy Editor
Proofreader
Sadie Kleinman
Sheilah Ledwidge, Word One
Editorial Director
Indexer
Robyn B. Siesky
Robert Swanson
Editorial Manager
Cover Image
Mary Beth Wakefield
© David Madison/Digital Vision/Jupiter Images
Production Manager Tim Tate
Acknowledgments Where to even begin? I guess I should start with my family and thank them for their understanding during the time it took me to write this book. From there, I think I will go with chronological order. I want to thank the crew from McGill University for giving me my first paid job as a web designer and John Tersigni, who gave me my first real project working with Flash and ActionScript. If you all had not done these things, I would not be where I am today. I also want to say thank you to Geoff Cook, Jeremy Zorn, and the rest of the crew at myYearbook .com. They gave me my first real job programming on a high-traffic website and were a great crew to work with. Then there is the crew at Purple Communications, who hired me to do Flex-PHP applications. You have definitely thrown some interesting projects my way and I have learned a great deal. And of course there is the crew at Wiley, who have been nothing but supportive and understanding. Specifically, I want to thank the entire editorial crew who went through great pains trying to figure out the esoteric rules for capitalizing variable and class names. I also want to thank the technical editor Jeffry House, who was always there with helpful suggestions and alternative ways of doing things. Finally, there are all of you, the readers. Without you, none of the efforts that went into this book mean anything. I hope you find this book a valuable resource.
Contents Introduction
Chapter 1: Introducing Cairngorm What Is Cairngorm? A Brief History of Cairngorm Overview of Cairngorm Cairngorm Source Code Cairngorm Organization and Documentation Major Components of Cairngorm
Basic Cairngorm Logic Flow Cairngorm Project Organization Benefits of Using Cairngorm Summary
Chapter 2: Frameworks and Design Patterns Types of Frameworks Micro-Architecture Design Patterns The Model View Controller Pattern The Observer Pattern The Singleton Pattern The Command Pattern The Proxy Pattern Summary
Chapter 3: The ServiceLocator What Is It? What Does It Look Like? IServiceLocator IServices
xxiii
1 1 2 3 3 3 6
7 9 10 11
13 13 14 15 17 21 22 23 25 26
29 29 30 30 31
Contents Responder AbstractServices ServiceLocator
How Do You Create It? How Do You Use It? Summary
Chapter 4: The ModelLocator What Is It? What Does It Look Like? How Do You Create It? How Do You Use It? Summary
Chapter 5: The FrontController What Is It? What Does It Look Like? How Do You Create It? How Do You Use It? Summary
Chapter 6: Events What Are They? What Do They Look Like? How Do You Create One? How Do You Use Them? Summary
Chapter 7: Commands What Are They? What Do They Look Like? How Do You Create One? How Do You Use Them? Summary
xvi
31 32 33
41 42 43
45 45 46 49 50 53
55 55 56 57 60 62
63 63 63 66 69 70
73 73 74 76 78 80
Contents Chapter 8: Delegates
81
What Are They? What Do They Look Like? How Do You Create One? How Do You Use Them? Summary
81 82 83 85 86
Chapter 9: Value Objects
89
What Are They? What Do They Look Like? How Do You Create One? How Do You Use Them? Summary
89 90 93 94 95
Chapter 10: How the Pieces Work Together A Simple Cairngorm Logic Flow Example View Dispatches Cairngorm Event FrontController Intercepts Event and Triggers Command Command Decides How to Handle Event Delegate Calls Remote Service Command Updates ModelLocator Changes in ModelLocator Are Broadcast via Data Binding
Summary
Chapter 11: Project Overview Meet the Client Project Overview Main Application User Registration Login and Logout Creating New Posts Loading of Existing Posts
97 97 98 99 99 101 102 103
104
105 105 106 106 107 107 108 108
xvii
Contents Commenting System Search Capabilities
Summary
Chapter 12: Flex Project Setup Why an AIR Project? Creating the Flex Project Linking Cairngorm to a Project Summary
Chapter 13: The Backend Description Database Classes Impact on Cairngorm Usage Summary
Chapter 14: Main Application Setup
108 109
109
111 111 112 115 118
119 119 119 121 122
123
Creating the FrontController Creating the ModelLocator Main Application Setup Summary
123 124 125 127
Chapter 15: User Registration
129
Registering Users Overview Value Objects Event Classes Delegate Classes Command Classes Views Implementation and Testing Summary
130 131 132 134 135 137 143 145
Chapter 16: User Login Login and Logout Overview Value Objects Event Classes Delegate Classes
xviii
147 147 148 148 149
Contents Command Classes Views Implementation and Testing Summary
Chapter 17: Adding Posts Adding Posts Overview Value Objects Event Classes Delegate Classes Command Classes Views Implementation and Testing Summary
Chapter 18: Loading Posts Loading Posts Overview Value Objects Event Classes Delegate Classes Command Classes Views Implementation and Testing Summary
Chapter 19: Adding a Commenting System Commenting Overview Value Objects Event Classes Delegate Classes Command Classes Views Implementation and Testing Summary
Chapter 20: Adding Search Capabilities Searching Overview Value Objects
150 152 154 156
159 159 160 161 162 164 167 169 172
173 173 174 174 175 176 179 180 183
185 185 186 187 188 189 192 195 198
201 201 202
xix
Contents Event Classes Delegate Classes Command Classes Views Implementation and Testing Summary
Chapter 21: Reviewing Version One The Application So Far How Value Objects Have Been Used How Events, Commands, and Delegates Have Been Used How Views Have Been Used Looking Toward Version 2 Summary
Chapter 22: Combining Classes Identifying Related Events and Delegates Events Delegates What About Commands?
Revising Registration Revising Login Revising Adding a Post Revising Loading Posts Revising Commenting Revising Searching Summary
Chapter 23: Calling Methods on Views The Problem ViewHelper and ViewLocator The iResponder Interface and the Responder Class Revising Registration Summary
Chapter 24: Sequencing Commands The SequenceCommand Class Revising Commenting Summary
xx
202 203 205 209 211 214
217 217 219 219 220 220 222
223 223 223 227 228
228 231 233 237 240 244 248
249 249 250 251 254 257
259 259 260 261
Contents Chapter 25: Criticisms of Cairngorm Criticisms of Cairngorm Data Binding Singled Out — Bad Singleton, Bad I Have To Write How Many Classes To Do That? Summary
Chapter 26: Best Practices Ten Tips Cairngorm Looks Complicated It Takes a Long Time To Do Things with Cairngorm Only Commands Update the Model Delegates Make Service Calls and Parse; Data/Commands Shouldn’t Parse Data Three Ways To Use Events, Commands, and Delegates ViewLocators and ViewHelpers Are Considered Bad Practice Use mx.rpc.Responder Instead of flash.net.Responder Don’t Have Views Use Cairngorm Events
RIA Development with Cairngorm: Adobe Max Presentation Summary
263 263 264 265 268 268
271 271 272 272 272 273 274 275 275 275
276 277
Chapter 27: Cairngorm Plug-in
279
Installation Cairngorm Locations Cairngorm Project Nature Create a New Controller Cairngorm Project Properties Create a New Command Customizing the Templates Known Issues Summary
279 285 287 289 293 296 299 299 300
Chapter 28: Cairngorm Extensions What Are They? Notifying Callers Event Generators Summary
301 301 303 306 307
xxi
Contents Chapter 29: Looking Back and Ahead Review Foundations of Cairngorm The Major Components of Cairngorm The Sample Application Criticisms and Best Practices Tools and Add-Ons
Online Resources Adobe Open Source Website cairngormdocs.org Adobe Cairngorm Forum Steven Webster’s and Alistair McLeod’s Blogs Adobe Developer Connection
Where To Go from Here Index
xxii
309 309 309 311 312 312 313
313 313 313 314 314 314
314 317
Introduction In this section you are introduced to the topics covered in this book, the assumptions being made about skill level, expected version of software, and instructions for downloading the source code used in this book.
Whom This Book Is For This is not a book on the basics of Flex. I am assuming that if you are interested in a topic like the Cairngorm framework, you know your way around the basics of Flex and are here to expand upon your existing skills. I am assuming knowledge of the following topics: ❑
Setting up a workspace in Flex Builder
❑
Creating a Flex project
❑
Working with remote data classes such as HTTPService, RemoteObject, and WebService
❑
Creating MXML-based components and including them in your main application
❑
The basics of object-oriented programming concepts as they apply to ActionScript, including: ❏
Packages
❏
Classes
❏
Importing
❏
Inheritance
❏
Interfaces
These topics may be discussed in the context of the Cairngorm framework, but I will assume that for the most part you are familiar with them. However, I may explain basic procedures such as how to create a class when Cairngorm-specific steps need to be taken.
What This Book Covers This book takes an in-depth look at the Cairngorm framework. It does so by examining the actual source code of the framework, looking at code from sample applications, and exploring the underlying principles that inform the design of the framework. You will be building several features of a blogging application using the Cairngorm framework. Chapter 1 starts by defining what Cairngorm is, its history, its structure, its major components, and some of the benefits of using the framework claimed by its creators.
Introduction Chapter 2 explores the concepts of frameworks and design patterns. It then examines each of the design patterns that form the foundation of the Cairngorm framework. Chapters 3 through 9 explore the major components of the Cairngorm framework. Each of these chapters starts with a basic description of the component in question. Next comes an examination of the source code for the classes and interfaces that make up the component. This is followed by instructions for creating the component. Each chapter then ends with examples of how the component is used. The major components examined in these chapters are: ❑
The ModelLocator
❑
The ServiceLocator
❑
Commands classes
❑
Events classes
❑
The FrontController
❑
Value objects classes
❑
Delegates classes
Chapter 10 then examines how all these pieces work together in a simple login application. Chapters 11 through 13 introduce you to the sample project that you will be creating and the setup required to make the application functional. The application is built with the Adobe Integrated Runtime environment to allow access to a local database file, eliminating the need to access remote services. The application is not intended to be complete, but rather to include enough features that you get a sense of working with the Cairngorm framework. Chapters 14 through 20 walk you through using Cairngorm in the “typical” way to build the features of the sample application. Chapter 21 reviews the features that you have built and examines how the various parts of Cairngorm were used in building those features. Chapters 22 through 24 examine alternative ways of using Cairngorm and, as examples, revise several of the features in the sample application. Chapter 25 examines some criticisms that have been made of the Cairngorm framework. Chapter 26 examines some of the ideas concerning best practices for using the Cairngorm framework. Chapter 27 walks you through the installation and use of the Cairngorm plug-in, which automates some of the more repetitive processes involved in creating Cairngorm classes. Chapter 28 introduces you to some of the prevalent features of the Universal Mind Cairngorm Extensions. Chapter 29 provides a review of the topics covered in this book and lists additional resources and suggestions for furthering your Cairngorm skills.
xxiv
Introduction
Software Versions The code in this book assumes you have the following software at the specified version: ❑
Flex Builder 3
❑
Flex SDK 3.1 or higher
❑
Adobe Integrated Runtime (AIR) 1.1 or higher
❑
Cairngorm 2.1
Source Code As you work through the examples in this book, you may choose either to type in all the code manually or to use the source code files that accompany the book. All the source code used in this book is available at www.wrox.com. The source code for a given chapter can be found in /Resources/code/chapter number/. For example, the code for Chapter 14 can be found in /Resources/code/ch14/. The example application in this book makes use of several utilities classes and an SQLite database file. These are used to make the application more realistic while avoiding the need to access data on a remote server. As you build the sample application, you will be required to update these resource files for each chapter to reflect the current state of the application. Using the final versions of these files would result in compiling errors because of references to classes that you won’t yet have created. You will be told at the beginning of each chapter if you are expected to update the files. By the time you complete Chapter 20 you will have created all the classes referenced by these utility files and will no longer have to update them.
How This Book Came To Be Early in my days as a Flex developer I did a lot of interviews for Flex positions. More often than not the question of my experience with Flex frameworks (mostly Cairngorm) came up and I would explain that while I was aware of the existence of the frameworks, I myself had never really had any use for them. This was true, but it did not seem to impress interviewers greatly. So after losing a couple of positions because of my “lack of experience” with Flex frameworks I set out to learn about as many of them as I could. What I found was that, while there were numerous frameworks, the learning resources out there, to put it in the politest of terms, left something to be desired. I could find only two books on Flex that even mentioned Cairngorm and none that mentioned any of the other frameworks I had researched (Mate, PureMVC, and Swiz). The two books that did mention Cairngorm each devoted only a chapter to it.
xxv
Introduction I was able to find some articles on Cairngorm and some sample applications built in Cairngorm, but many of the articles focused more on the thought behind Cairngorm than on practical use, and many of the applications either were too simple (“Hello World”) or were outdated. It was not that there weren’t people out there trying to provide information; it just seemed there was nothing that really explained the framework in detail from the ground up. At the time I was doing this research, I happened to be working for an agency writing technical articles. This agency sent out an e-mail requesting ideas for book topics. Mostly out of my own frustration at the lack of resources I had found on Cairngorm, I suggested it as topic for a book, and here we are.
The Book ’ s Approach As mentioned in the previous section, part of the motivation for this book was the lack of resources on the topic. To further complicate things, the few resources that were available often varied greatly in their approaches to using Cairngorm, as you will see when you examine the sample applications used in this book. In every field of study there are topics that can spur endless debate, and in some cases, heated arguments. As it turns out, many of the topics covered in this book, including the Cairngorm framework itself, fall into this category. The approach I take is to examine the varying ways that things have been done using the Cairngorm framework and the varying ideas on the concepts related to Cairngorm, and then either suggest an approach or simply note that you as the developer can choose whatever way you prefer.
Errata I would love to tell you that I and the rest of the team here at Wiley are so good at what we do that we caught every mistake and inconsistency in this book before it hit the press, but that is probably not going to be the case. If you notice a mistake with anything in this book, either in text or code, please let us know. By sending in errata you may save another reader hours of frustration, and at the same time you will be helping us provide even higher quality information. To find the errata page for this book, go to www.wrox.com and locate the title using the Search box or one of the title lists. Then, on the book details page, click the Book Errata link. On this page you can view all the errata that have been submitted for this book and posted by Wrox editors. A complete book list including links to each book’s errata is also available at www.wrox.com/misc-pages/booklist .shtml.
If you don’t spot “your” error on the Book Errata page, go to www.wrox.com/contact/techsupport .shtml and complete the form there to send us the error you have found. We’ll check the information and, if appropriate, post a message to the book’s Errata page and fix the problem in subsequent editions of the book.
xxvi
Introduction
Open Source Notifications This book reproduces portions of the following projects: ❑
Cairngorm source code: http://opensource.adobe.com/svn/opensource/cairngorm/ trunk/frameworks/cairngorm/
❑
CairngormStore 2.1 example: http://www.cairngormdocs.org/exampleApps/ CairngormStoreWeb2_1.zip
❑
FStop application: http://download.macromedia.com/pub/developer/ f3ic_studentFiles_16Jun08.zip
❑
Cairngorm plug-in: http://opensource.adobe.com/wiki/display/cairngorm/Plugin
❑
Cairngorm Extensions - http://code.google.com/p/flexcairngorm/
The CairngormStore 2.1 example, FStop application, and Cairngorm plug-in are used according to following terms: Copyright © 2007. Adobe Systems Incorporated. All rights reserved. The Cairngorm Extensions are used according to the following conditions: Copyright © 2008, Universal Mind. All rights reserved. Author: Thomas Burleson, Principal Architect
[email protected], Darron Schall, Principal Architect.
xxvii
Introducing Cairngorm In this chapter you are introduced to Cairngorm, its history, and basic structure. You will start by examining some definitions of what Cairngorm is. Then you will be given a brief history of how Cairngorm came to be. Finally, you will be given a broad overview of the basic parts that make up Cairngorm, its organization, and its handling of application logic.
What Is Cairngorm? Depending on what source you go to, you will find varying descriptions of what Cairngorm is. The Adobe Labs web site (the current home of the Cairngorm project) describes it as follows: Cairngorm is the lightweight micro-architecture for Rich Internet Applications built in Flex or AIR. A collaboration of recognized design patterns, Cairngorm exemplifies and encourages best-practices for RIA development advocated by Adobe Consulting [and] encourages best-practice leverage of the underlying Flex framework, while making it easier for medium to large teams of software engineers [to] deliver medium to large scale, mission-critical Rich Internet Applications (http://opensource .adobe.com/wiki/display/cairngorm/Cairngorm).
The Wikipedia entry for Cairngorm has the following description: Cairngorm is based on the MVC model. It is specifically designed to facilitate complex state and data synchronization between the client and the server, while keeping the programming of the View layer detached from the data implementation (http://en.wikipedia.org/wiki/ Cairngorm_(Flex_framework)).
A further description of its intended use can be found at http://opensource.adobe.com/wiki/display/cairngorm/About: The Cairngorm micro-architecture is intended as a framework for Enterprise RIA developers. . . . The benefits of the Cairngorm architecture are realized when developing complex RIA applications with multiple use-cases and views, with a team of developers, and with a multi-disciplinary development team that includes designers as well as creative and technical developers.
Chapter 1: Introducing Cairngorm You probably won’t fully understand these descriptions at this point. Later sections of the book will explore the concepts that inform Cairngorm (such as design patterns) and the individual pieces that make up the Cairngorm framework. However, before moving on to examining the concepts and structure of the Cairngorm framework, I want to point out a few things that will make those discussions more informative. You will see Cairngorm referred to as both a framework and a micro-architecture (as can be seen in the preceding quotations). Both are technically correct, with micro-architecture being the more specific of the two terms (that is, the one that describes Cairngorm on a more detailed level). The differences between a framework and an micro-architecture will be discussed in later chapters. For now, just be aware that if you see something describing “the Cairngorm micro-architecture,” it is referring to the same thing as the Cairngorm framework. Regardless of the differences you may find in descriptions of Cairngorm, most of them agree on the following: ❑
Cairngorm is targeted at the rapid development of rich Internet applications (RIAs) using the Flex framework.
❑
Cairngorm facilitates team development.
❑
Cairngorm is suited for medium- to large-scale projects in which code needs to be separated and organized.
❑
The Cairngorm framework endorses and encourages best practices for building Flex-based RIAs.
You will see in later chapters of this book that even these points are not without their critics and challengers.
A Brief History of Cairngorm Cairngorm currently exists as an open source project on the Adobe Open Source web site (http:// opensource.adobe.com/wiki/display/cairngorm/Cairngorm). However, Cairngorm was not created by Adobe. In fact, its creation is not even tied to the origin of the Flex framework. Cairngorm was created by a company named iteration::two in Edinburgh, Scotland; the company was founded by Steven Webster and Alistair McLeod. The name Cairngorm actually comes from a range of mountains in the eastern Highlands of Scotland. Iteration::two was eventually was acquired by Macromedia and became Adobe Consulting. Cairngorm’s roots stretch back as far as Flash MX in the book Reality J2EE—Architecting for Flash MX (Steven Webster and Alistair McLeod, Pearson Education, 2003). As the technologies for RIA matured to ActionScript 2.0 and Flash Remoting with Flash MX 2004, the ideas behind Cairngorm were revisited in the section on “ActionScript 2.0 design patterns for rich Internet applications” in the ActionScript 2.0 Dictionary (Steven Webster and Alistair McLeod, Macromedia Press, 2003). The ideas expressed in these books originated from a subset of design patterns, advocated by Sun Microsystems, found in the Core J2EE Pattern Catalog (http://java.sun.com/blueprints/corej2eepatterns/Patterns/ index.html).
2
Chapter 1: Introducing Cairngorm Essentially, as Flash started introducing such capabilities as remoting, it started to face many of the same challenges previously faced in the world of J2EE RIA development. Therefore, many of the same solutions applied in the world of J2EE were now applicable to Flash development. By solutions, we are referring to design patterns, the specifics of which will be covered in the next chapter. It was not until later that the aforementioned ideas were applied to the Flex framework in the book entitled Developing Rich Clients with Macromedia Flex (Steven Webster and Alistair McLeod, Macromedia Press, 2004). This was the first book on best practices for enterprise RIA development with Adobe Flex. Since then Cairngorm has become one of the best-known frameworks for Flex development, and at the time of this writing it is the only one found on the Adobe Open Source web site. It officially became an open source project at the Macromedia MAX2004 conference in New Orleans in November 2004. The first open source release of the Cairngorm framework was labeled the 0.95 version for Flex 1.5, with an updated 0.99 version (with new features) released soon thereafter. The version at the time of writing is Cairngorm 2.2, released on April 30, 2007. You can read about future plans for Cairngorm in a blog post by Steven Webster at http://weblogs .macromedia.com/swebster/archives/2008/08/cairngorm_3_-_a.html.
Over view of Cairngorm This section is meant as a broad overview of the major components that make up the Cairngorm framework. Subsequent chapters will examine each of these parts in more detail.
Cairngorm Source Code Because it is an open source project on the Adobe Open Source web site, you have full access to the source code for Cairngorm. The code is stored in a Subversion repository. Directions for installing a Subversion client and for checking out the code can be found at http://opensource.adobe.com/wiki/display/ cairngorm/Get+Source+Code. It is not necessary to check out the source code for the purposes of this book, but if you want to, that option is available to you.
Cairngorm Organization and Documentation The documentation and class structure outline for Cairngorm can be found at http://cairngormdocs .org/docs/cairngorm_2_2_1/index.html. Much like the standard Flex framework, Cairngorm’s classes are organized into packages. These are: ❑
com.adobe.cairngorm
❑
com.adobe.cairngorm.business
❑
com.adobe.cairngorm.commands
❑
com.adobe.cairngorm.control
3
Chapter 1: Introducing Cairngorm ❑
com.adobe.cairngorm.model
❑
com.adobe.cairngorm.view
❑
com.adobe.cairngorm.vo
It is not necessary to know the contents of each package in order to build a Cairngorm project, but knowing the basic structure will help you understand how the framework is organized and, should you need to locate a particular class, make that easier to do. When applicable, this documentation will be referenced as you explore the major components of Cairngorm that are commonly used to build Cairngorm projects. For now, here is a brief explanation of the classes and interfaces that you can find in each package:
com.adobe.cairngorm The com.adobe.cairngorm package contains the following classes: ❑
CairngormError: An error class thrown when a Cairngorm error occurs
❑
CairngormMessageCodes: Stores Cairngorm message codes
com.adobe.cairngorm.business The com.adobe.cairngorm.business package contains the following classes: ❑
AbstractServices: Used to manage all services defined on the IServiceLocator instance
❑
ServiceLocator: Allows services to be located and security credentials to be managed
It also contains the following interfaces: ❑
IServiceLocator: Defines an interface for the service locator
❑
IServices: Defines an interface for managing services on an IServiceLocator
❑
Responder: Implemented by classes to handle data returned as the result of a service call to the server. Deprecated as of Cairngorm 2.1 and replaced by mx.rpc.IResponder
com.adobe.cairngorm.commands The com.adobe.cairngorm.commands package contains the following class: ❑
SequenceCommand: A “pseudo-abstract” (since ActionScript has no real concept of abstract classes) base class that can be extended when you wish to chain commands together for a single user gesture, or establish some simple form of decision-based workflow
It also contains the following interfaces:
4
❑
Command: Enforces the contract between the FrontController and concrete command classes in your application. Deprecated as of Cairngorm 2.1 and replaced by com.adobe.cairngorm .commands.ICommand
❑
ICommand: Enforces the contract between the FrontController and concrete command classes in your application
Chapter 1: Introducing Cairngorm com.adobe.cairngorm.control The com.adobe.cairngorm.control package contains the following classes: ❑
CairngormEvent: Used to differentiate Cairngorm events from events raised by the underlying Flex framework or Flash Player
❑
CairngormEventDispatcher: A singleton class used by the application developer to broadcast events that correspond to user gestures and requests
❑
FrontController: A base class for an application-specific FrontController, able to dispatch control to appropriate command classes following particular user gestures
com.adobe.cairngorm.model The com.adobe.cairngorm.model package contains the following class: ❑
ModelLocator: Marker interface used to mark the custom ModelLocator. Deprecated as of Cairngorm 2.1 and replaced by com.adobe.cairngorm.model.IModelLocator
It also contains the following interface: ❑
IModelLocator: The new Marker interface used to mark the custom ModelLocator
com.adobe.cairngorm.view The com.adobe.cairngorm.view package contains the following classes: ❑
ViewHelper: Used to isolate command classes from the implementation details of a view. Deprecated as of Cairngorm 2.1
❑
ViewLocator: A singleton class used to retrieve ViewHelper classes that can manipulate (get/ set/switch) the user interface of a Cairngorm RIA. Deprecated as of Cairngorm 2.1
com.adobe.cairngorm.vo The com.adobe.cairngorm.vo package contains the following class: ❑
ValueObject: A marker interface that improves readability of code by identifying the classes within a Cairngorm application that are to be used as value objects for passing data between tiers of an application. Deprecated as of Cairngorm 2.1 and replaced by com.adobe.cairngorm. vo.IValueObject
It also contains the following interface: ❑
IValueObject: A new marker interface that improves readability of code by identifying the classes within a Cairngorm application that are to be used as value objects for passing data between tiers of an application
As you can see from the class and interface descriptions, classes and interfaces are still included in the framework despite having been deprecated.
5
Chapter 1: Introducing Cairngorm You can also see that a number of interfaces (e.g., IValueObject) simply serve as markers to identify types of classes. Additionally, as you will see in the next section, which discusses the major components for Cairngorm, not all these classes are directly used in building Cairngorm projects. However, they are all part of the Cairngorm framework and the documentation exists should you need to refer to it.
Major Components of Cairngorm In the article entitled Flex 3: Introducing Cairngorm (Adobe Customer Training/Partner Enablement Group), Thomas Burleson of Universal Mind, and Leo Shuman of Adobe Customer Training, under the guidance of Matt Boles (http://www.adobe.com/devnet/flex/articles/introducing_ cairngorm.html), the major components of Cairngorm are defined as follows: ❑
ModelLocator
❑
Services
❑
Commands
❑
Events
❑
Controller
This list is a good starting point for understanding the components of Cairngorm, but it mixes specific components with general concepts. For example, the first item in the list is an actual class name found in the framework; services are used in the framework but are actually represented by a class called the ServiceLocator; commands and events are types of classes; and the controller is represented by a class called the FrontController. In addition to components in this list, there are two other types of classes commonly used in Cairngorm that are not included in the list. These are value objects and delegates. The most likely reason for their omission from the list is that their use is generally considered optional. However, there is a com.adobe .cairngorm.vo package for value objects and, while there is no delegate class or interface in Cairngorm, their use is common enough to warrant examination as well. This book will therefore be defining the major components of Cairngorm in terms of the classes commonly used in Cairngorm projects: ❑
ModelLocator
❑
ServiceLocator
❑
Commands
❑
Events
❑
FrontController
❑
Value objects
❑
Delegates
A chapter will be devoted to each of these components. For now, here is a brief description of each.
6
Chapter 1: Introducing Cairngorm ModelLocator The ModelLocator serves as a central repository for shared application data. It allows components of the application to have access to the same set of data to ensure synchronization. Additionally, through Flex’s data binding mechanism, it notifies views of any changes to the data so that the views can update themselves to reflect those changes.
ServiceLocator Most RIA applications require communication with a backend system. This communication with the backend is generally accomplished with one of the remoting classes, such as HTTPService, WebService, or RemoteObject. The ServiceLocator provides a centralized location for access and sharing such services so that they do not have to be declared in each component that needs to access the service.
Commands Command classes are triggered by Cairngorm event classes to trigger business logic and update the ModelLocator. In standard Cairngorm practice only a command should ever update the ModelLocator.
Events Event classes are used to trigger command classes and pass along any data they require.
FrontController The FrontController provides a centralized location for defining how Cairngorm events are handled. This class registers event-command relationships and triggers the registered command for a given event when that event is fired.
Value Objects Value objects are classes that represent conceptually related sets of data. Value objects contain public properties that represent the individual pieces of data being represented. They are commonly used to represent data in views by binding to their properties, and to transfer related sets of data between classes in the application and the server.
Delegates Delegates are classes responsible for accessing remote services. They serve as proxy objects that take care of the details of accessing a particular service and return the data from the service via responder functions.
Basic Cairngorm Logic Flow Now that you have a sense of the major components that make up Cairngorm, the following is a broad overview of how logic is implemented in a Cairngorm application. The basic chain of events in a Cairngorm application can be seen in Figure 1-1.
7
Chapter 1: Introducing Cairngorm Delegate locates service
CairngormEvent dispatched
SeviceLocator returns service
FrontController finds command class and calls execute function Command executes
Yes
Delegate calls service
Accesses remote data?
Command updates ModelLocator
Backend returns result
ModelLocator updates views via data binding Views updated
Figure 1-1
A view (or in some cases some other event, such as the loading of the application) dispatches a Cairngorm event. When this occurs, the FrontController intercepts the event and triggers the command class registered for that event. At this point the command class can do one of several things. If no communication with the backend is required, the command may simply update the ModelLocator, causing the views to be updated via data binding. If delegate classes are being used, a function on the delegate class will be called to perform actions on the backend via a remoting class found in the ServiceLocator. In this case the command acts as a responder to the remote call by registering itself with the delegate class. When the delegate function completes its call, the command class is passed the status of the call and any data returned from it via responder functions. If the command class is directly calling the remote service, it performs the lookup on the ServiceLocator class and directly registers itself as a responder on the remote service. Once again, the results of the call are handled via responder functions defined in the command class.
8
Chapter 1: Introducing Cairngorm Once the command class has performed any remote calls, it updates the ModelLocator. Once again, this causes any views that are binding to properties on the ModelLocator to update themselves.
Cairngorm Project Organization Not only is the Cairngorm framework organized into packages, but so are projects created with the framework. When you build a project in Cairngorm, the classes you create get organized into specific packages. You do this by creating sub-folders in your project’s src folder. The particular organization of these packages can vary greatly. For example, the Cairngorm Store application (http://www.cairngormdocs.org/ exampleApps/CairngormStoreWeb2_1.zip) organizes its packages as seen in Figure 1-2.
Figure 1-2 However, if you look at the FStop application included in the article “Flex 3: Introducing Cairngorm” (cited earlier in this chapter), it organizes its packages as seen in Figure 1-3.
Figure 1-3 Both of these package structures work; however, it is not always clear where you would expect to find a given type of class. For example, in the Cairngorm Store, events are in a package on the same level as the business package, while in the FStop application, events are a subpackage of the business package.
9
Chapter 1: Introducing Cairngorm To avoid any confusion as to where a particular type of class should be located, I am suggesting the following package structure: ❑
commands (stores command classes)
❑
controllers (stores the FrontController class)
❑
delegates (stores service delegate classes)
❑
events (stores event classes)
❑
models (stores ModelLocator classes)
❑
services (stores the ServiceLocator class)
❑
valueobjects (stores value object classes)
❑
views (stores MXML views)
In the above package structure, each type of class is contained in a package that describes the type of classes it contains. This is the package structure that you will use when creating the sample application in this book. However, as we explore the various parts of the Cairngorm framework, the various locations that the classes can be located in will be pointed out, using the Cairngorm Store and FStop applications as examples. This knowledge can be useful if you inherit a project from another developer.
Benefits of Using Cairngorm Now that you have a basic idea of the structure, components, and application logic flow of a Cairngorm project, it’s time to examine some of the benefits the framework provides. In the “Flex 3: Introducing Cairngorm” article cited earlier, the following purposes of Cairngorm are defined: ❑
Cairngorm is an approach to organizing and partitioning: ❏
Code and packages
❏
Component functionality and roles
❑
Cairngorm is a “best practice” methodology for Flex software design and development.
❑
Cairngorm encourages developers to identify, organize, and separate code based on its roles/ responsibilities.
Many of these points have already been mentioned in the quoted descriptions of Cairngorm earlier in this chapter, but what has not been discussed are the benefits that arise from this organization and bestpractices methodology.
10
Chapter 1: Introducing Cairngorm The “Flex 3: Introducing Cairngorm” article outlines several benefits of using Cairngorm. These include: ❑
Cairngorm is ideal for team development, since it allows designers, component developers, and data-services developers to work in parallel.
❑
Maintenance, debugging, and the adding or updating of features are easier than with a project created using just the standard Flex framework.
Both of these benefits arise from the organization provided by the Cairngorm framework. Since code is organized by roles/responsibilities and is implemented via the Cairngorm methodology, designers can be working on views while component developers and service developers work on their respective parts. When these pieces of code are brought together into the application you can be pretty sure they will be compatible, since they are all using the standards defined by the Cairngorm framework. This allows application features to be developed in parallel, shortening development timelines. Maintenance, debugging, and the adding or updating of features become easier because you know where to look to find the code related to a given feature. If you have ever developed an application in Flash, or looked at one developed by someone else, you most likely have spent time searching for code, since it can be spread across frames or hidden in deeply nested movie clips. The same situation can occur in Flex if code is scattered across the application or application components. However, when using Cairngorm you can quickly track down specific code because of the predictable way Cairngorm handles application logic. If you see that a view is dispatching a given event, you can look for the command registered for that event in the FrontController and quickly see what logic the command is triggering. Adding and updating features are simply matters of modifying existing Cairngorm classes or adding new ones.
Summary In this chapter you were introduced to the Cairngorm framework, including its history, guiding principles, major components, application logic flow, and organization. Cairngorm promotes the separation of code by roles/responsibilities and facilitates team development, in that different parts of the application can be worked on in parallel. As I mentioned in the history section, Cairngorm borrows many of its solutions from the J2EE world. If you are coming from a computer programming background or have previously worked with Java or some other object-oriented language, many of these solutions may be familiar to you. If you are coming from more of an ActionScript development background, they may be less familiar, since ActionScript is a less rigid language than Java and standards and practices still vary greatly in the world of ActionScript development. Regardless of your current experience, the beginning chapters of this book discuss the underlying principles that inform Cairngorm and look in more detail at the major components defined in this chapter. After examining these components, you will complete a brief sample application so that you can see examples of how these components work together to form application features. Once you have completed the sample application, criticism and best practices related to Cairngorm will be discussed, as these will be more informative after you have worked with the framework.
11
Frameworks and Design Patterns In this chapter you’ll examine the concept of frameworks, micro-architectures, and design patterns. You will then examine the specific design patterns that make up or inform the design of the Cairngorm framework.
Types of Frameworks In part one of the six-part article titled Developing Flex RIAs with Cairngorm Micro-Architecture (Steven Webster and Leon Tanner) at http://www.adobe.com/devnet/flex/articles/ cairngorm_pt1.html, the following statement is made: “In software development, framework is among the most overloaded and overused terms.” While there is much debate as to what does and does not constitute a framework and even further debate about how to categorize frameworks, there are some generally accepted definitions of the types of frameworks. Two of these are of particular interest with regard to Flex and Cairngorm. The first type is an application framework, which, according to Webster and Tanner, the Flex framework is. They define this type of framework as follows: When a framework features highly granular class libraries that provide a high degree of flexibility to application developers, or when a framework provides application-level services that are useful across multiple developer projects, we call it an “application framework.”
In an application framework, it is up to the developer to decide how the classes of the framework are assembled to create an application. You can think of this type of framework as being made of Lego blocks. Lego blocks come in various shapes and sizes. Users are free to assemble them into larger objects as they see fit.
Chapter 2: Frameworks and Design Patterns The second type of framework is an architectural framework. According to Webster and Tanner, Cairngorm is this type of framework. They describe this type as follows: Typically, the job of an architectural framework is not to provide any additional services to application developers beyond an infrastructure around which an application can hang. Architectural frameworks provide the skeleton or internal structure of moving parts, around which the flesh and muscle particular to a business domain can be layered. In other words, an architectural framework provides a generic starting point for the technical architecture of your application.
You can conceptualize this type of framework as the framing of a house. The framing does not dictate what type of wiring, plumbing, or appliances you use, but it does enforce constraints on where such things need to be located within the framing. Additionally, the framing does not add any functionality to the wiring, plumbing, and appliances; it only dictates the structure through which they are added to the house. Other than to provide general background information, there is another reason I’m taking the time to go over these differences between types of frameworks — namely, to prepare you for the experience of working with Cairngorm. If you have worked only with the Flex framework, then Cairngorm (or any other architectural framework, for that matter) is going to seem much more rigid and formulaic. But as discussed in the previous chapter, it’s actually this formulaic aspect that provides the benefits, such as facilitation of team development, of using such a framework.
Micro - Architecture Recall that in the last chapter I pointed out that you will see Cairngorm referred to as both a framework and a micro-architecture, with micro-architecture being the more specific of the two terms. Webster and Tanner define a micro-architecture as a collection of design patterns (discussed shortly) that have been proven to work well together. Cairngorm comprises the following design patterns: ❑
Model View Controller
❑
Observer
❑
Singleton
❑
Command
❑
Proxy
After a discussion of what a design pattern is as a general concept, each of the individual patterns listed earlier will be described in more detail, as will its use in the Cairngorm framework.
14
Chapter 2: Frameworks and Design Patterns
Design Patterns If you have been programming for any length of time, chances are you have heard the term design patterns. The history of design patterns goes back as far as 1977, when Christopher Alexander originally developed them as an architectural concept. (In architecture, patterns refer to the idea of capturing architectural design ideas as archetypal and reusable descriptions.) In 1987, Kent Beck and Ward Cunningham began applying the concept of patterns to programming and presented their results at the Object Oriented Programming, Systems, Languages and Applications (OOPSLA) conference that year. While design patterns have been around for some time, they started gaining popularity in computer science after the publishing of the book Design Patterns: Elements of Reusable Object-Oriented Software (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides: Addison-Wesley, 1994) commonly referred to as the gang of four (GOF) book. The GOF book is generally considered the “canonical” text on design patterns. According to this book, design patterns can be broken into four essential elements: ❑
Pattern name
❑
Problem
❑
Solution
❑
Consequences
The pattern name is a way of referring to and describing a design problem, its solution, and the solution’s consequences. In other words, it is a simple way of referring to the collection of the other three parts. The problem describes when to apply a given pattern. This description may be a specific design problem such as how to represent an algorithm with objects, or it may describe a combination of classes that results in an inflexible design, or it may simply be a list of conditions that must be met before it makes sense to use a given pattern. The solution describes the parts that make up the design, the relationships between them, the responsibilities of each part, and how those parts work together. Solutions are not concrete implementations, but rather templates that can be applied to multiple situations. In other words, there is not a specific set of code or implementation that one can point to and call a particular pattern; rather, patterns describe a general arrangement of elements (classes or objects) that solves a given problem. The consequences describe the pros and cons of using a given solution. All systems have their strengths and weaknesses, and the process of choosing a solution is often a matter of finding one in which the pros outweigh the cons.
15
Chapter 2: Frameworks and Design Patterns If this description sounds vague, it should. Design patterns are general abstract concepts that have no specific implementation. As the father of patterns, Christopher Alexander, puts it: Each pattern describes a problem that occurs over and over in our environment, and then describes the core of the solution to that problem in such a way that you can use this solution a million times over, without ever doing it the same way twice.
While Christopher Alexander was referring to the realm of building architecture, the statement holds true for applications as well. While the general definition of design patterns is vague, the GOF book does divide them into three separate categories. These are: ❑
Creational
❑
Structural
❑
Behavioral
Creational patterns are concerned with the process of creating other objects. Examples of such patterns include: ❑
Factory method: Defines an interface for creating an object, but let subclasses decide which class to instantiate
❑
Builder: Separates the construction of a complex object from its representation so that the same construction process can create different representations
❑
Singleton: Ensures a class has only one instance, and provides a global point of access to it
Structural patterns are concerned with the composition of classes and objects. Examples of these include: ❑
Adapter or wrapper: Converts the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces
❑
Composite: Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly
❑
Proxy: Provides a surrogate or placeholder for another object to control access to it
Behavioral patterns are concerned with the way in which objects interact and distribute responsibility. Examples of these include: ❑
Command: Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queues, or log requests, and support operations that can be undone
❑
Observer or publish/subscribe: Defines a one-to-many dependency among objects so that when one object changes state, all its dependents are notified and updated automatically
❑
Iterator: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation
Some resources define additional categories, but also include the ones defined in the GOF book. A more comprehensive list of the design patterns and the categories they fall into can be found at http:// en.wikipedia.org/wiki/Design_pattern_(computer_science)#Domain_specific_patterns.
16
Chapter 2: Frameworks and Design Patterns The design patterns that make up Cairngorm fall into the following categories: ❑
Observer or publish/subscribe: Behavioral
❑
Singleton: Creational
❑
Command: Behavioral
❑
Proxy: Structural
Note that the Model View Controller is not included in this list. The GOF book talks about this pattern only briefly (on pages 4–6), but does not include it in any of the categories it defines; nor does the referenced list from Wikipedia. The Wikipedia entry for the Model View Controller describes it as both an architectural pattern and a design pattern, and says the following when talking about it as a design pattern: “MVC encompasses more of the architecture of an application than is typical for a design pattern” (http://en.wikipedia .org/wiki/Model-view-controller#As_a_design_pattern). The MVC pattern is not so much a general solution to a particular problem as it is a collection of principles that guides how an application is organized. The MVC and the other design patterns are examined in more detail in the sections that follow.
The Model View Controller Pattern As mentioned in the last section, the MVC pattern is generally more concerned than others with how an application is structured than it is in the solution to a particular problem. The general idea is to divide application code into three tiers called the model, the view, and the controller. The GOF book describes the MVC pattern as follows: MVC consists of three kinds of objects. The Model is the application object, the View is the screen presentation, and the Controller defines the way that the user interface reacts to user input.
In applications that do not use the MVC pattern, these three tiers tend to be mixed together and scattered across the application. This makes changing any given part of the application more difficult, since to change something like the way that a view represents its data, you may have to update the underlying data. Additionally, the same data may be duplicated across multiple views, requiring each view to be updated. In addition to splitting applications into tiers, the MVC pattern also dictates how these tiers interact, the responsibilities of each tier, and which tiers have knowledge of the other tiers. The book Head First Design Patterns (Eric Freeman, Elisabeth Robson, Kathy Sierra, and Bert Bates, O’Reilly, 2004) describes the responsibilities of each tier as follows: ❑
Model: Holds the data, state, and application logic
❑
View: Presents the data contained in the model to the user
❑
Controller: Takes user input and decides what it means to the model
17
Chapter 2: Frameworks and Design Patterns A more detailed description of the roles of the tiers and how they communicate can be found in the book Advanced ActionScript 3 with Design Patterns (Joey Lott and Danny Patterson, Adobe Press, 2006). This book describes the defining characteristics of each tier as follows: ❑
The model is responsible for storing data in a way that is independent of the view and the controller. The model should never have any knowledge of either the controller or the view.
❑
The view uses data from the model to draw itself. The key to the view is that it contains only the visual elements and the code necessary to read data from the model and to use that data as necessary for the user interface.
❑
The controller is responsible for taking actions (e.g., because the user does something). Each of these tiers has a specific relationship to the other tiers. The model must always remain independent of the view and the controller. This allows the model to be used with different views and controllers.
❑
The view has direct knowledge of the model because it uses data from the model to draw itself. However, the view must never directly update the model; this is always done via the controller.
❑
The controller knows about the model because it is responsible for updating the model based upon user actions. The relationship between views and controllers is often one-to-one. The view contains the user interface, but lets the controller determine how user gestures are handled.
The basic logical flow of an MVC application can be seen in Figure 2-1.
Updated view presented to user.
User
User clicks some interface element.
Gets data from model and displays it
View
Decides how to handle actions
Controller may update view directly (e.g. enable/disable buttons).
Controller
Stores application data, state, and logic
Model notifies views that data has changed.
Figure 2-1
18
Model
Controller decides how to handle the click by asking the model to do something.
Chapter 2: Frameworks and Design Patterns You will find varying descriptions regarding the responsibilities of each tier and the level of communication allowed between tiers, depending on the source you are looking at. However, the general rule is that the less the tiers know about each other, the more flexible the design is. Regardless, the basic idea behind separating an application into model, view, and controller remains the same. These tiers allow modification of the individual parts of the application with a minimal impact on the other parts, and therefore promote reuse. For example, you can easily switch out views as long as the views are replaced by other views that know how to use the model, or you can switch the controller associated with a particular view and get different behaviors from the same interface. Additionally, multiple views can use the same model as long as they know how to interpret it without having to re-create the data on each view. In the description of Cairngorm from Wikipedia, Cairngorm is described as being “based on the MVC model.” The term “based on” becomes particularly important since the framework does deviate from typical implementations of the MVC pattern in a number of ways. These deviations will be discussed shortly. For now, here is a basic overview of how the MVC pattern informs the design of Cairngorm. This idea of Cairngorm being informed by the MVC paradigm can be seen in the article referred to in the last chapter, “Flex 3: Introducing Cairngorm” (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles). The article maps the roles and responsibilities of a Cairngorm application to various parts of the MVC paradigm, as shown in Figure 2-2. Role Types
Cairngorm Layer
Data
Model Communicates via data binding
Updates data
GUI
View Communicates via Cairngorm Events
Business and Data Access
Controller
Figure 2-2 The article describes the roles and responsibilities of the three tiers as follows: ❑
The model holds data objects and represents the state of the data.
❑
The controller processes business logic.
❑
Views render data and communicate with the controller by dispatching events.
19
Chapter 2: Frameworks and Design Patterns ❑
Views watch the model with data bindings. ❏
Views are graphical user interfaces (GUIs).
❏
Views are usually composites of UI controls or other views.
❏
Views can contain child views.
Now that you have a basic understanding of the MVC patterns, the logic flow diagram of Cairngorm can be seen in a different light (Figure 2-3).
Delegate locates service
CairngormEvent dispatched
SeviceLocator returns service
FrontController finds command class and calls execute function Command executes
Yes
Delegate calls service
Accesses remote data?
Command updates ModelLocator
Backend returns result
ModelLocator updates views via data binding Views updated
Figure 2-3
A view dispatching Cairngorm events that are intercepted by the FrontController is the MVC equivalent of a view passing off user inputs to a controller class. However, in this case there is not the usual one-toone correspondence between views and controllers. Instead, all Cairngorm events are passed to the same controller, which decides how to handle the event by looking up the command class that is registered for that event. The command class then takes on the responsibility normally given to a controller class to update the model. This may, and often does, include accessing a remote data store via remoting services and using that data to update the model.
20
Chapter 2: Frameworks and Design Patterns The ModelLocator is still responsible for notifying views of any changes. In Cairngorm it does this with Flex’s data binding. However, as you will see when the ModelLocator is discussed in more detail, it deviates from the classical MVC model in that within the Cairngorm framework the ModelLocator is not supposed to hold any application logic. So while Cairngorm may deviate from the “standard” implementation of the MVC pattern in a number of ways, it should be easy to see that, at the very least, the MVC pattern seems to inform the design of Cairngorm — hence the term “based on” in the Wikipedia definition.
The Obser ver Pattern The GOF book defines the intent of the observer pattern as being to define a one-to-many relationship between objects so that when the state of an object changes, all its dependents are notified. The relationships among objects in the observer pattern are described in terms of subjects and observers. Subjects can have any number of dependent observers. Observers are notified whenever the subject undergoes a change in state. In response, each observer queries the subject to synchronize its state with the subject’s state. This relationship is also described as a publisher-subscriber relationship: the subject publishes notifications and the observers subscribe to be notified of these changes. This arrangement allows the subject to notify its subscribers without having to know who they are. ActionScript has an implementation of this pattern in its event listener model. Chances are you have done something like the following pseudo-code: someObject.addEventListener(MouseEvent.CLICK,someFunction); someObject.addEventListener(MouseEvent.CLICK,someOtherFunction);
In this example someObject would have to directly extend the EventDispatcher class (http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/ EventDispatcher.html) or some class that extends it in its ancestor chain. In this case someObject is the subject or publisher and someFunction and someOtherFunction are the observers or subscribers. When someObject is clicked, the dispatchEvent function inherited from the EventDispatcher class will be called with the MouseEvent.CLICK event type, and the functions that have been registered as listeners will be called and passed the MouseEvent object. Multiple functions can be registered to the same event or to other events on the same publisher object. This results in the one-to-many relationship referred to in the description of the intent of the observer pattern. In a typical MVC design the model has methods that allow objects (typically view classes) to register themselves as listeners for a particular event. When something occurs that causes the model to update, the model then notifies any registered listeners. In Cairngorm this notification is accomplished via data binding. Views bind to properties on the ModelLocator: when these properties change, views are notified via data binding so they can update their data.
21
Chapter 2: Frameworks and Design Patterns
The Singleton Pattern The GOF book defines the intent of the singleton pattern as being to ensure that a class has only one instance and that global access is provided to that single instance. Singletons are generally used in situations where data needs to be kept synchronized or for centralizing shared resources. Take for example the situation in which two views display and allow the user to update data, but hold two separate instances of that data. When one view updates the data, the other view will be out of sync. However, if both views are pointing to the same object, this problem no longer exists; all you have to do is refresh the views (which you would probably do by implementing the observer pattern on the model with the views as observers). Another possibility is two views that need to access the same external data source via a service call. If each view has its own instance of the service call, you now have to maintain that code in two different locations and you will be creating duplicate resources (in the form of objects) that do essentially the same thing. Again, this is not an issue if both views are referencing the same object. A typical singleton class is made up of three major parts. These are: ❑
A static variable that holds a reference to the single instance.
❑
A public static method (most commonly called getInstance) that provides global access to the stored instance if one already exists and creates it if it does not.
❑
Some means of limiting the class to a single instance.
A typical singleton class might look like the following pseudo-code: package com.somedomain.somepackage{ private static instance:SingletonExample; private function SingletonExample():void{ //any code need to initialize the object } public static function getInstance():SingletonExample{ if (SingletonExample.instance == null){ SingletonExample.instance = new SingletonExample(); } return SingletonExample.instance; } }
The single instance of the class is held in the private static instance property. The constructor of the class is set to private so that it cannot be accessed from outside the class. The public static method getInstance provides the only method for retrieving the instance, ensuring that all classes that make use of it are referencing the same instance.
22
Chapter 2: Frameworks and Design Patterns The implementation of a singleton class in ActionScript is slightly different, since ActionScript does not allow for private constructors. You can simulate a private constructor by using an internal class, as in the following example: package{ public class Singleton { private static var instance:Singleton; public function Singleton(enforcer:SingletonEnforcer){} public static function getInstance():Singleton { if (Singleton.instance == null) { Singleton.instance = new Singleton(new SingletonEnforcer); } return Singleton.instance; } } } class SingletonEnforcer{}
The class SingletonEnforcer is defined in the same file as the singleton class, but outside the package definition. When a class is defined in this manner in ActionScript, then only the main class defined in the file (the one inside the package definition) may access it. Since the constructor requires an instance of the SingletonEnforcer, any attempt to directly instantiate the class using the constructor will fail because of an invalid parameter error. However, since the getInstance function is internal to the class, it may freely access the SingletonEnforcer class and pass it to the constructor. Cairngorm employs several singleton objects. These include: ❑
The ModelLocator
❑
The ServiceLocator
❑
The FrontController
The ModelLocator provides global access to data, the ServiceLocator provides global access to remoting services, and the FrontController provides a centralized location for registering and responding to Cairngorm events.
The Command Pattern The GOF book defines the intent of the command pattern as being to encapsulate a request in an object that lets you parameterize clients with different requests, queue or log requests, and support undoable operations.
23
Chapter 2: Frameworks and Design Patterns The idea behind the command pattern is to provide a predictable interface without any other class that uses it having to worry about the details of execution. In its simplest form, the command pattern consists of six elements. These are: ❑
Command interface
❑
Concrete command
❑
Receiver
❑
Client
❑
Invoker
The command interface is simple and defines only one method, execute, as seen in the following pseudo-code: package com.somedomain.somepackage{ public interface ICommand{ function execute():void; } }
The concrete command is the class that actually implements the ICommand interface, and the receiver is the class that is the target of the operation. For example: package com.somedomain.somepackage{ import com.somedomain.somepackage.ICommand; public class TurnOnLight implements ICommand{ private var light:Light; public function TurnOnLight(light:Light):void{ this.light = light; } public function execute():void{ this.light.turnOn(); } } }
In the preceding example, TurnOnLight is the concrete command and the Light object held in the light property of the command is the receiver. The client is the object that instantiates the command class, and the invoker is the object that calls the execute function. The class invoking an object that implements the command pattern does not have to know anything about what the class actually does other than that it represents a particular request (e.g., turn on the light). The invoker has to know only a predictable method that needs to be called. This method is most often named execute. In Cairngorm, if a class implements the command pattern you know that it will contain a method, execute, that triggers the logic encapsulated by the command, since it will implement the ICommand interface. Additionally, since all commands implement an execute function, they can easily be swapped with one another to provide different functionality in the invoking classes.
24
Chapter 2: Frameworks and Design Patterns Cairngorm implements the command pattern in its command classes. Each of these classes has a method called execute, enforced via the Cairngorm ICommand interface, that triggers the execution of its internal logic. Commands are triggered by dispatching Cairngorm event classes. This means that the views do not have to know anything about the logic they trigger; they have to know only what event to fire. Furthermore, you can easily change the command that an event triggers simply by updating the event-command pairing in the FrontController. In terms of the actors in the pattern, the Cairngorm command is the concrete command. The FrontController is always the invoker and the client, as it is responsible for creating the command object and invoking the execute command. The receiver is often a delegate class or remote object accessed via the ServiceLocator.
The Proxy Pattern The GOF book defines the intent of the Proxy pattern as an object that acts as a placeholder or surrogate for another object to control access to that object. Proxies come in various types, but as far as Cairngorm is concerned, you are interested in the type known as a remote proxy. A remote proxy is an object that controls access to data from a remote source. An example of such an object might look like the following pseudo-code: package com.somedomain.somepackage{ import flash.events.ResultEvent; import mx.rpc.http.HTTPService; import mx.rpc.AsyncToken; public class DataLoader extends EventDispatcher{ public function DataLoader():void{} private function onResult(event:ResultEvent):void{ dispatchEvent(new DataEvent(Event.COMPLETE, false, false, XML(event.target.data))); } public function loadData():void{ var service:HTTPService = new HTTPService(“someUrl”); service.addEventListener(ResultEvent.RESULT, this.onResult); service.send(); } } }
A class using this proxy might look something like the following: package com.somedomain.somepackage{ import flash.events.ResultEvent; import com.somedomain.somepackage.DataLoader; public class ProxyExample{ public function ProxyExample():void{ var dataLoader:DataLoader = new DataLoader(); dataLoader.addEventListener(Event.COMPLETE, this,onDataLoaded); dataLoader.loadData(); }
25
Chapter 2: Frameworks and Design Patterns private function onDataLoaded(event:ResultEvent):void{ trace (event.data); } } }
In the preceding example, the DataLoader class stands in for and controls access to the actual service call that loads the data. RIAs commonly access data from remote sources via remoting classes such as HTTPService. In Cairngorm these services are registered in the ServiceLocator and are commonly accessed via delegate classes. When delegates are used to access services, they become proxy objects representing and controlling access to those services.
Summary In this chapter you examined the concepts of frameworks, micro-architectures, and design patterns. Two types of frameworks were examined, application and architectural. Application frameworks (Flex) provide classes with a high level of granular functionality and leave it up to the developer to decide how to assemble an application, while architectural frameworks (Cairngorm) provide a defined structure that an application is built around, but generally do not add functionality to it. The concept of a micro-architecture was defined as a set of design patterns that have proven to work well together. Micro-architecture is a more accurate term than framework for what the Cairngorm framework is. The basic concept of design patterns was examined. Design patterns are not specific implementations of code, but rather general descriptions of how recurring problems can be solved. Design patterns can be generally categorized into three types: ❑
Creational
❑
Structural
❑
Behavioral
Creational patterns are concerned with the process of creating other objects; structural patterns are concerned with the composition of classes and objects; and behavioral patterns are concerned with the way in which objects interact and distribute responsibility. You examined the specific design patterns that make up the Cairngorm micro-architecture. These are:
26
❑
Model View Controller
❑
Observer
❑
Singleton
❑
Command
❑
Proxy
Chapter 2: Frameworks and Design Patterns The MVC design pattern is more of a set of guidelines that dictate how the code of an application is organized and how the pieces of an application interact with one another. MVC splits an application into model, view, and controller tiers and provides guidelines for how these tiers communicate with one another. It was also pointed out that, while the MVC pattern informs the design of Cairngorm, Cairngorm deviates from the typical MVC paradigm in several ways. These include the ModelLocator ’s not containing any application logic and the FrontController ’s being the single controller class as opposed to there being a one-to-one correspondence between views and controllers. The observer pattern allows for dependent objects to remain synchronized via the publisher-subscriber paradigm, in which the publisher notifies its subscribers of changes in its state by broadcasting notifications. In Cairngorm the observer pattern is achieved by views binding to properties on the ModelLocator. The singleton pattern allows only one instance of a class to be created by storing that instance in a private static variable and providing a single access point to it via a public static function called getInstance. Cairngorm contains several singleton classes including the ModelLocator, ServiceLocator, and FrontController. The command pattern encapsulates a request in an object and provides a consistent interface for triggering that request via a method named execute. Cairngorm implements the command pattern in its command classes via the ICommand interface. The proxy pattern allows a class to control access to another object. Delegate classes are commonly used as proxies in Cairngorm projects to access remote services. Now that you have been introduced to the basic concepts behind Cairngorm, it is time to start taking a closer look at the major components that make it up.
27
The ServiceLocator In this chapter you’ll take a detailed look at the Cairngorm classes and interfaces related to the ServiceLocator component of the Cairngorm framework. Portions of the code in this chapter are from the Cairngorm SVN repository located at http:// opensource.adobe.com/svn/opensource/cairngorm. The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/wiki/display/ cairngorm/license, and is also referenced in the Open Source Notifications section of the Introduction to this book.
What Is It? The ServiceLocator is one of the core singleton classes of the Cairngorm framework. According to a previously cited article, “Flex 3: Introducing Cairngorm” (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles), the ServiceLocator has the following characteristics: ❑
The ServiceLocator pattern is used to create a global singleton registry to centralize all instances of Flex RDS components used in an application. These include: ❏
HTTPService
❏
WebService
❏
RemoteObject
❑
It supports easy lookup of services by name.
❑
It should never be used outside the control layer.
In a standard Flex project, services may be instantiated as needed by individual components. This can cause them to be scattered across the application. In Cairngorm all services are created in and accessed by the ServiceLocator. Because the ServiceLocator provides a centralized location for
Chapter 3: The ServiceLocator registering and accessing services, individual components no longer have to instantiate services themselves and services can all be configured in a single location.
What Does It Look Like? The classes and interfaces associated with the ServiceLocator are found in the com.adobe.cairngorm .business package (http://cairngormdocs.org/docs/cairngorm_2_2_1/com/adobe/cairngorm/ business/package-detail.html). The following classes and interfaces are found in this package: ❑
❑
Interfaces ❏
IServiceLocator: Defines an interface for the service locator
❏
IServices: Defines an interface for managing services on an IServiceLocator
❏
Responder: Implemented by classes that wish to handle data returned as the result of a service call to the server. Deprecated as of Cairngorm 2.1 and replaced by mx.rpc .IResponder
Classes ❏
AbstractServices: Used to manage all services defined on the IServiceLocator instance
❏
ServiceLocator: Allows services to be located and security credentials to be managed
In the sections that follow you will take a closer look at each of these classes and interfaces, as well as some classes found in the source code that are not included in the documentation.
IServiceLocator This interface provides the basic method signatures for retrieving the various types of services that can be registered. The code for this interface is as follows: package com.adobe.cairngorm.business { import mx.messaging.MessageAgent; import mx.rpc.AbstractService; import mx.rpc.http.HTTPService; import mx.rpc.remoting.RemoteObject; import mx.rpc.soap.WebService; public interface IServiceLocator { function getHTTPService( serviceId : String ) : HTTPService; function getRemoteObject( serviceId : String ) : RemoteObject; function getWebService( destinationId : String ) : WebService; function setCredentials( username : String, password : String ) : void; function setRemoteCredentials( username : String, password : String ) : void function logout() : void; function set timeout( timeoutTime : int ):void function get timeout(): int } }
30
Chapter 3: The ServiceLocator The getHTTPService, getRemoteObject, and getWebService functions each retrieves its respective type of service using a string matching the id that the service is registered with. There are two functions for setting credentials: setCredentials and setRemoteCredentials. The difference between the two functions is that services using a proxy may require you to use the latter. The logout function logs you out of all registered services. Finally, there are getter and setter functions for a timeout parameter that is used to set the timeout of the registered services.
IServices The IServices interface contains a more generic set of functions for retrieving and registering services. The code for this interface is as follows: package com.adobe.cairngorm.business { public interface IServices { function register( serviceLocator : IServiceLocator ) : void; function getService( name : String ) : Object; function setCredentials( username : String, password : String ) : void; function setRemoteCredentials(username : String, password : String ) : void; function release() : void; function logout() : void; function set timeout( timeoutTime : int ) : void; function get timeout() : int; } }
The register function takes in an IServiceLocator instance and registers it. The getService function takes in a string as a parameter and returns the service with the id matching the string. The setCredentials and setRemoteCredentials functions do the same thing as in the IServiceLocator interface, and the difference between them is the same. The release function releases the resources held by the services. The logout function logs the user out of all registered services. The getter and setter functions for the timeout property again set the timeout interval for all registered services.
Responder The Cairngorm responder interface has been deprecated. If you need to use a responder interface, use Flex’s mx.rpc.IResponder. You will see examples of using this interface later in the book.
31
Chapter 3: The ServiceLocator
AbstractServices The AbstractServices class implements the IServices interface. The purpose of this class is to manage all the services registered on an instance of an IServiceLocator. If you have worked with classes in ActionScript before, you may be wondering about the abstract part of this class, as ActionScript does not support true abstract classes. If you look at the source for this class, you will see that the creators of Cairngorm got around this lack of support by throwing errors in the abstract methods: package com.adobe.cairngorm.business { import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; import flash.utils.describeType; public class AbstractServices implements IServices { private static const FLEX_VERSION : String = “FlexVersion”; private static const AUTHENTICATED : String = “authenticated”; private var _timeout : int = 0; public function register( serviceLocator : IServiceLocator ) : void { throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “register” ); } public function getService( name : String ) : Object { throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “getService” ); } public function setCredentials( username : String, password : String ) : void { throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “setCredentials” ); } public function setRemoteCredentials( username : String, password : String ) : void{ throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “setRemoteCredentials” ); } public function release() : void { throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “release” ); } public function logout() : void { throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “logout” ); } public function set timeout( timeoutTime : int ) : void { _timeout = timeoutTime;
32
Chapter 3: The ServiceLocator } public function get timeout() : int { return _timeout; } protected function getAccessors( serviceLocator : IServiceLocator ) : XMLList { var description : XML = describeType( serviceLocator ); var accessors : XMLList = description.accessor.( @access == “readwrite” ).@name; return accessors; } protected function serviceLogout( service : Object ) : void { throw new CairngormError( CairngormMessageCodes.ABSTRACT_METHOD_CALLED, “serviceLogout” ); } } }
In addition to implementing the IServices interface, this class includes a function called getAccessors that returns an XMLList containing the inheritance chain of an IServiceLocator instance.
ServiceLocator This is the class that you will actually use in your projects. It implements the IServiceLocator interface. The code for this class is as follows: package com.adobe.cairngorm.business { import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; import mx.rpc.AbstractInvoker; import mx.rpc.AbstractService; import mx.rpc.http.HTTPService; import mx.rpc.remoting.RemoteObject; import mx.rpc.soap.WebService; public class ServiceLocator implements IServiceLocator { private static var _instance : ServiceLocator; private var _httpServices : HTTPServices; private var _remoteObjects : RemoteObjects; private var _webServices : WebServices; private var _timeout : int = 0; public static function get instance() : ServiceLocator { if ( ! _instance ) { _instance = new ServiceLocator(); } return _instance; }
33
Chapter 3: The ServiceLocator public static function getInstance() : ServiceLocator { return instance; } // Constructor should be private but current AS3.0 does not allow it public function ServiceLocator() { if ( _instance ) { throw new CairngormError( CairngormMessageCodes.SINGLETON_EXCEPTION, “ServiceLocator” ); } _instance = this; } [Deprecated(“You should now use one of the strongly typed methods for returning a service.”)] public function getService( serviceId : String ) : AbstractService { return AbstractService( getServiceForId( serviceId ) ); } [Deprecated(“You should now use one of the strongly typed methods for returning a service.”)] public function getInvokerService( serviceId : String ) : AbstractInvoker { return AbstractInvoker( getServiceForId( serviceId ) ); } public function getHTTPService( name : String ) : HTTPService { return HTTPService( httpServices.getService( name ) ); } public function getRemoteObject( name : String ) : RemoteObject { return RemoteObject( remoteObjects.getService( name ) ); } public function getWebService( name : String ) : WebService { return WebService( webServices.getService( name ) ); } public function setCredentials( username : String, password : String ) : void { httpServices.setCredentials( username, password ); remoteObjects.setCredentials( username, password ); webServices.setCredentials( username, password ); } public function setRemoteCredentials( username : String, password : String ) : void{ httpServices.setRemoteCredentials( username, password ); remoteObjects.setRemoteCredentials( username, password ); webServices.setRemoteCredentials( username, password ); } public function logout() : void { httpServices.release();
34
Chapter 3: The ServiceLocator remoteObjects.release(); webServices.release(); httpServices.logout(); remoteObjects.logout(); webServices.logout(); } public function set timeout( timeoutTime : int ) : void { _timeout = timeoutTime; } public function get timeout() : int { return _timeout; } protected function get httpServices() : HTTPServices { if ( _httpServices == null ) { _httpServices = new HTTPServices(); _httpServices.timeout = timeout; _httpServices.register( this ); } return _httpServices; } protected function get remoteObjects() : RemoteObjects { if ( _remoteObjects == null ) { _remoteObjects = new RemoteObjects(); _remoteObjects.timeout = timeout; _remoteObjects.register( this ); } return _remoteObjects; } protected function get webServices() : WebServices { if ( _webServices == null ) { _webServices = new WebServices(); _webServices.timeout = timeout; _webServices.register( this ); } return _webServices; } private function getServiceForId( serviceId : String ) : Object { if ( this[ serviceId ] == null ) { throw new CairngormError( CairngormMessageCodes.SERVICE_NOT_FOUND, serviceId ); } return this[ serviceId ]; } } }
35
Chapter 3: The ServiceLocator Here you can see an actual implementation of a singleton class. The private static variable _instance holds the single instance of the class. The class actually provides two public static methods for accessing the instance. The more typical of the two (as you would see it in other languages), getInstance, calls the ActionScript getter function. The ActionScript getter function instance then checks to see if the instance has been created; if not, it creates the instance and then returns it. You can also see in the constructor that there are other methods ensuring that the constructor cannot be directly called, despite the lack of a private constructor in ActionScript. In this method you simply throw an error if there is already an instance held in the _instance variable. Note that the getService and getInvokerService have been deprecated in favor of the strongly typed functions getHTTPService, getRemoteObject, and getWebService. The older functions enabled you to get any type of service by using the id by returning AbstractService and AbstractInvoker types. The newer functions enable you to predetermine the specific type of service being returned, which is generally considered a better practice. If you take a look at the first few variables of this class, _httpServices, _remoteObjects, and _webServices, you will notice that they are the plural forms of the services that the ServiceLocator enables you to register. These properties are accessed by the protected httpServices, remoteObjects, and webServices setter functions. While not included in the documentation, these classes also exist in the com.adobe.cairngorm.business package. It is these classes that extend the AbstractService class. The code for each class is as follows:
HTTPService package com.adobe.cairngorm.business { import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; import flash.utils.Dictionary; import mx.rpc.http.HTTPService; internal class HTTPServices extends AbstractServices { private var services : Dictionary = new Dictionary(); public override function register( serviceLocator : IServiceLocator ) : void { var accessors : XMLList = getAccessors( serviceLocator ); for ( var i : uint = 0; i < accessors.length(); i++ ) { var name : String = accessors[ i ]; var obj : Object = serviceLocator[ name ]; if ( obj is HTTPService ) { HTTPService( obj ).requestTimeout = timeout; services[ name ] = obj; } } } public override function getService( name : String ) : Object
36
Chapter 3: The ServiceLocator { var service : HTTPService = services[ name ]; if ( service == null ) { throw new CairngormError( CairngormMessageCodes.HTTP_SERVICE_NOT_FOUND, name ); } return service; } public override function setCredentials( username : String, password : String ) : void { for ( var name : String in services ) { var service : HTTPService = services[ name ]; service.setCredentials( username, password ); } } public override function setRemoteCredentials( username : String, password : String ) : void { for ( var name : String in services ) { var service : HTTPService = services[ name ]; service.setRemoteCredentials( username, password ); } } public override function release() : void { // do nothing. } public override function logout() : void { for ( var name : String in services ) { var service : HTTPService = services[ name ]; serviceLogout( service ); } } protected override function serviceLogout( service : Object ) : void { var httpService : HTTPService = HTTPService( service ); if ( httpService.channelSet.authenticated ) { httpService.logout(); } } } }
37
Chapter 3: The ServiceLocator RemoteObjects package com.adobe.cairngorm.business { import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; import flash.utils.Dictionary; import mx.rpc.remoting.RemoteObject; internal class RemoteObjects extends AbstractServices { private var services : Dictionary = new Dictionary(); public override function register( serviceLocator : IServiceLocator ) : void { var accessors : XMLList = getAccessors( serviceLocator ); for ( var i : uint = 0; i < accessors.length(); i++ ) { var name : String = accessors[ i ]; var obj : Object = serviceLocator[ name ]; if ( obj is RemoteObject ) { RemoteObject( obj ).requestTimeout = timeout; services[ name ] = obj; } } } public override function getService( name : String ) : Object { var service : RemoteObject = services[ name ]; if ( service == null ) { throw new CairngormError( CairngormMessageCodes.REMOTE_OBJECT_NOT_FOUND, name ); } return service; } public override function setCredentials( username : String, password : String ) : void { for ( var name : String in services ) { var service : RemoteObject = services[ name ]; service.setCredentials( username, password ); } } public override function setRemoteCredentials( username : String, password : String ) : void { for ( var name : String in services ) { var service : RemoteObject = services[ name ]; service.setRemoteCredentials( username, password ); } } public override function release() : void
38
Chapter 3: The ServiceLocator { // do nothing. } public override function logout() : void { for ( var name : String in services ) { var service : RemoteObject = services[ name ]; serviceLogout( service ); } } protected override function serviceLogout( service : Object ) : void { var remoteService : RemoteObject = RemoteObject( service ); if ( remoteService.channelSet.authenticated ) { remoteService.logout(); } } } }
WebServices package com.adobe.cairngorm.business { import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; import flash.utils.Dictionary; import mx.rpc.soap.WebService; internal class WebServices extends AbstractServices { private var services : Dictionary = new Dictionary(); public override function register( serviceLocator : IServiceLocator ) : void { var accessors : XMLList = getAccessors( serviceLocator ); for ( var i : uint = 0; i < accessors.length(); i++ ) { var name : String = accessors[ i ]; var obj : Object = serviceLocator[ name ]; if ( obj is WebService ) { var webService : WebService = WebService( obj ); webService.loadWSDL(); webService.requestTimeout = timeout; services[ name ] = obj; } } } public override function getService( name : String ) : Object { var service : WebService = services[ name ]; if ( service == null )
39
Chapter 3: The ServiceLocator { throw new CairngormError( CairngormMessageCodes.WEB_SERVICE_NOT_FOUND, name ); } return service; } public override function setCredentials( username : String, password : String ) : void { for ( var name : String in services ) { var service : WebService = services[ name ]; service.setCredentials( username, password ); } } public override function setRemoteCredentials( username : String, password : String ) : void { for ( var name : String in services ) { var service : WebService = services[ name ]; service.setRemoteCredentials( username, password ); } } public override function release() : void { // do nothing. } public override function logout() : void { for ( var name : String in services ) { var service : WebService = services[ name ]; serviceLogout( service ); } } protected override function serviceLogout( service : Object ) : void { var webService : WebService = WebService( service ); if ( webService.channelSet.authenticated ) { webService.logout(); } } } }
40
Chapter 3: The ServiceLocator Note that each of these classes stores the collection of the services that it represents in an instance of the Dictionary class. Each class implements the register function, which takes in an instance of an IServiceLocator. It then calls the getAccessors function from the AbstractServices class and loops through each of the ancestors. If a given ancestor is of the type that the class is responsible for, that ancestor gets stored in the Dictionary object. Once the object has been registered in the Dictionary, each class defines a getService function that retrieves the given service from the Dictionary, casting it to the appropriate type or throwing an error if it is not found. All the remaining functions in these classes retrieve instances from the dictionary and call functions on those instances to do things like set credentials. To return to the ServiceLocator class, an instance of each of these classes is contained in each of the _httpServices, _remoteObjects, and _webServices variables. Each variable manages the collection of services that are referred to by its namesake. RemoteObjects are managed by the _remoteObjects variable, HTTPServices are managed by the _httpServices variable, and WebService instances are managed by the _webServices variable. All services are accessed via the methods in the ServiceLocator class.
How Do You Create It? In a typical Cairngorm project, the ServiceLocator is created in the business package (Figure 3-1). This is the case in both the Cairngorm Store application and the FStop application.
Figure 3-1
Creating the ServiceLocator is a bit different from creating the other Cairngorm classes in that the ServiceLocator is created as an MXML file. It’s simple to create a ServiceLocator. In the business package, create a new component. Some developers simply name the file Services; others include the project name, as in Figure 3-2.
41
Chapter 3: The ServiceLocator
Figure 3-2
You will have to fully type in the name ServiceLocator in the “Based on” field, since the ServiceLocator will not be an option in the list. Once the component has been created, you need to make a few modifications. You need to change the default namespace from “*” to something like xmlns:services=“com.adobe.cairngorm.business.*”. Once you have done that, you need to add the prefix you created to the beginning and ending tags, as in the following:
How Do You Use It? Now that you have the component created, you will add services to it as you would to any MXML file. The following code contains examples of adding each type of service that the ServiceLocator can use:
42
Chapter 3: The ServiceLocator
In order to use the ServiceLocator in your components, there must be an instance of it in your application, normally in the main application file. To include the ServiceLocator component you created, create a namespace in the main application tag pointing to the location where you created the component. Then simply add an instance of the ServiceLocator you created to the main application, as in the following:
To access a service contained in the ServiceLocator, use the following code: //HTTPService var service:HTTPService = ServiceLocator.getInstance().getHTTPService(‘someHTTPService); //Remote Object var remoteObject = ServiceLocator.getInstance().getRemoteObject(‘someRemoteObject); //WebService var webService:WebService = ServiceLocator.getInstance().getWebService(‘someWebService’);
Note that you are not referring to the ServiceLocator via the instance name that you gave it in the main application file. You need an instance of the file you created to be somewhere in the application; however, once that instance is included in the application, you access the services using the ServiceLocator class name. Once you have retrieved an instance of a service from the ServiceLocator, working with it becomes the same as working with that service in any Flex project. Recall that the ServiceLocator is not intended to be used outside the control layer. Most often the ServiceLocator is accessed in command or delegate classes.
Summary In this chapter you looked at the classes and interfaces associated with the Cairngorm ServiceLocator. These included: ❑
AbstractServices
❑
HTTPServices
❑
IServiceLocator
❑
IServices
❑
RemoteObjects
43
Chapter 3: The ServiceLocator ❑
Responder
❑
ServiceLocator
❑
WebServices
The ServiceLocator acts a centralized repository where your remote classes such as HTTPService, WebService, and RemoteObject are created and accessed. Services are looked up by instance name using to corresponding function for the type of service that you are looking for. These functions are getHTTPService, getRemoteObject, and getWebService. The ServiceLocator is not intended to be used outside the control layer. The ServiceLocator is implemented as an MXML file, normally in the business package. You register services by adding the appropriate tags to the ServiceLocator MXML file. You need to have an instance of the ServiceLocator MXML file in your main application; however, this instance is not directly accessed. Instead you use the singleton getInstance method to refer to the instance and then call the desired methods on the returned instance. Once you have retrieved a service from the ServiceLocator, working with it becomes the same as working with that service in any Flex project.
44
The ModelLocator In this chapter you’ll take a detailed look at the Cairngorm classes and interfaces related to the ModelLocator component of the Cairngorm framework. Portions of the code in this chapter are from the Cairngorm SVN repository, located at http:// opensource.adobe.com/svn/opensource/cairngorm. The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/wiki/display/ cairngorm/license, and is also referenced in the introduction to this book.
What Is It? The ModelLocator is one of the core singleton classes of the Cairngorm framework. The ModelLocator serves as a central repository for storing data that needs to be accessed in multiple locations. According to the article cited in earlier chapters, “Flex 3: Introducing Cairngorm” (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles), the ModelLocator has the following characteristics: ❑
It is a global singleton repository for shared or global data.
❑
It does not load or persist data to a permanent data store.
❑
It does not contain business logic.
❑
It supports data binding for auto-notifications of data changes to views.
The ModelLocator class acts as a centralized location for accessing shared data. This data is shared via public properties on the ModelLocator class. Classes that need to access this data retrieve the single instance of the ModelLocator using the static getInstance method and then refer to the public properties. The ModelLocator differs from other possible implementations of the MVC pattern in that it does not contain any application logic. In some implementations of the MVC pattern the model contains
Chapter 4: The ModelLocator functions for registering the listeners and then notifying them of changes in the data (observer pattern). Cairngorm performs these actions through the Flex data-binding mechanism, and that is how you should have views subscribe to changes in the state of the properties held in the ModelLocator.
What Does It Look Like? The interfaces associated with the ModelLocator are found in the com.adobe.cairngorm.model package. The following interfaces are found in this package: ❑
IModelLocator: Marker interface used to mark the custom ModelLocator.
❑
ModelLocator: Deprecated as of Cairngorm 2.1 and replaced by com.adobe.cairngorm.model. IModelLocator.
The code for the IModelLocator interface is as follows: package com.adobe.cairngorm.model { public interface IModelLocator { } }
No methods are actually defined in the interface. The only purpose of this interface is to allow data typing to mark a class as an instance of a ModelLocator. Many developers do not even bother implementing the interface in their ModelLocator classes. Such an implementation might look like the following: package model { import mx.collections.ArrayCollection; import com.someproject.vo.UserVo; [Bindable] public class ModelLocator { static private var __instance:ModelLocator; public var user:UserVO; public var items:ArrayCollection=new ArrayCollection(); public var purchasedItems:ArrayCollection=new ArrayCollection(); public function ModelLocator() { if ( modelLocator != null ) { throw new Error( “Only one ModelLocator instance should be instantiated”); } } static public function getInstance():ModelLocator {
46
Chapter 4: The ModelLocator if(__instance == null) { __instance=new ModelLocator(); } return __instance; } } }
In the preceding example you can again see the implementation of the singleton pattern with the private static instance variable and the getInstance methods that control access to it. The data that you wish to access is stored as public properties in the ModelLocator class. These properties are often instances of one of the collection classes (e.g., ArrayCollection) or value object classes, as can be seen in the items, purchasedItems, and user properties in the preceding example. But the properties can be of any valid data type. A more complete example of a ModelLocator can be seen in the classic Cairngorm store example application (http://www.cairngormdocs.org/exampleApps/CairngormStoreWeb.zip). This application contains several model classes (which is an acceptable practice). The following is the code from the ShopModelLocator class: package com.adobe.cairngorm.samples.store.model { import mx.collections.ICollectionView; import mx.formatters.CurrencyFormatter; import com.adobe.cairngorm.model.ModelLocator; import com.adobe.cairngorm.samples.store.model.ShoppingCart; import com.adobe.cairngorm.samples.store.util.Comparator; import com.adobe.cairngorm.samples.store.view.assets.CairngormStoreAssets; import com.adobe.cairngorm.samples.store.vo.ProductVO; import com.adobe.cairngorm.samples.store.view.checkout.PaymentInformationModel; import com.adobe.cairngorm.samples.store.view.checkout.GeneralInformationModel; import mx.collections.ArrayCollection; [Bindable] public class ShopModelLocator implements ModelLocator { private static var modelLocator : ShopModelLocator; public static function getInstance() : ShopModelLocator { if ( modelLocator == null ) { modelLocator = new ShopModelLocator(); } return modelLocator; } //Constructor should be private but current AS3.0 does not allow it yet (?) public function ShopModelLocator() { if ( modelLocator != null ) {
47
Chapter 4: The ModelLocator throw new Error( “Only one ShopModelLocator instance should beinstantiated” ); } shoppingCart = new ShoppingCart(); productComparator = new Comparator(); currencyFormatter = getInitialisedFormatter(); assets = new CairngormStoreAssets(); } private function getInitialisedFormatter() : CurrencyFormatter { var formatter:CurrencyFormatter = new CurrencyFormatter(); formatter.currencySymbol = “$”; formatter.precision = 2; return formatter; } public var products : ICollectionView; public var selectedItem : ProductVO; public var shoppingCart : ShoppingCart; public var productComparator : Comparator; public var currencyFormatter : CurrencyFormatter; public var assets : CairngormStoreAssets; public var orderConfirmed : Boolean; public var creditCardInvalid : Boolean; public var cartEmpty : Boolean; public var formIncomplete : Boolean; public var generalInfo : GeneralInformationModel = new GeneralInformationModel(); public var paymentInfo : PaymentInformationModel = new PaymentInformationModel(); public var paymentValidators : ArrayCollection = new ArrayCollection(); public var generalInfoValidators : ArrayCollection = new ArrayCollection(); public var workflowState : Number = VIEWING_PRODUCTS_IN_THUMBNAILS; public static var VIEWING_PRODUCTS_IN_THUMBNAILS : Number = 0; public static var VIEWING_PRODUCTS_IN_GRID : Number = 1; public static var VIEWING_CHECKOUT : Number = 2; } }
Here you can see numerous examples of public properties of various different types, including static constants and references to other model classes. Do note, though, that this sample application uses an older version of Cairngorm, as you can see from its implementing the deprecated ModelLocator interface. However, the way the properties are being implemented is still typical.
48
Chapter 4: The ModelLocator
How Do You Create It? Since implementing the IModelLocator is optional, it is up to you if you wish to select that interface during the class creation process in Flex Builder. Whether you implement the IModelLocator interface or not, you will still need to implement the singleton pattern. In a typical Cairngorm project, the ModelLocator is created in the model package, as is done in the FStop application seen in Figure 4-1.
Figure 4-1
It is also acceptable to have multiple model classes as seen in the Cairngorm Store application (Figure 4-2).
Figure 4-2 The general naming convention for ModelLocator classes is to prefix the word model or locator with something describing what the model represents, as in Figure 4-2. Some developers use the application name as prefix when there is only one ModelLocator class for the entire application. To create a ModelLocator simply give it a name as seen in Figure 4-3 and click the Finish button.
49
Chapter 4: The ModelLocator
Figure 4-3
Once you have the basic class created, you add the private static instance variable and getInstance function, as in the following: package com.cairngormexample.model { public class CairngormExampleModel { private static var instance:CairngormExampleModel; public function CairngormExampleModel(se:SingletonEnforcer) { } public static function getInstance():CairngormExampleModel{ if (CairngormExampleModel.instance == null){ CairngormExampleModel.instance = new CairngormExampleModel(new SingletonEnforcer()); } return CairngormExampleModel.instance; } } } class SingletonEnforcer{};
How Do You Use It? Once you have your ModelLocator class created, the next step is to add public properties representing the data that needs to be accessed to the ModelLocator. As mentioned previously, in many cases this data consists either of one of the Flex mx.collections classes or instances of value object classes, as can be seen in the following:
50
Chapter 4: The ModelLocator package com.cairngormexample.model { import mx.collections.ArrayCollection; import com.someproject.vo.UserVO; public class CairngormExampleModel { private static var instance:CairngormExampleModel; public var user:UserVO; public var items:ArrayCollection=new ArrayCollection(); public var purchasedItems:ArrayCollection=new ArrayCollection(); public function CairngormExampleModel(se:SingletonEnforcer) { } public static function getInstance():CairngormExampleModel{ if (CairngormExampleModel.instance == null){ CairngormExampleModel.instance = new CairngormExampleModel(new SingletonEnforcer()); } return CairngormExampleModel.instance; } } } class SingletonEnforcer{};
Once these properties have been added you simply need to get a reference to the ModelLocator and then reference the properties, as in the following: var model: CairngormExampleModel = CairngormExampleModel.getInstance(); //user model.user; //items model.items //purchased items model.purchasedItems
Once you have this reference you can use the properties of the ModelLocator for data binding in components. The following example is again from the Cairngorm store application. This code is from the ProductsAndCheckoutViewStack component, which makes use of the previously examined ShopModelLocator class:
A reference to the ModelLocator class is created at the top of the file. This reference is then used to bind to various properties on the ModelLocator, such as in the GraphicalProductList, TextualProductList, and Checkout components seen in the code above. These views will now be notified of any changes to the data contained in the ModelLocator class via these bindings.
52
Chapter 4: The ModelLocator
Summary In this chapter you examined the ModelLocator class and the associated IModelLocator interface. The ModelLocator has the following characteristics: ❑
It is a global singleton repository for shared or global data.
❑
It does not load or persist data to a permanent data store.
❑
It does not contain business logic.
❑
It supports data binding for auto-notifications of data changes to views.
The IModelLocator interface does not define any functions, but rather serves as a data type marker. Some developers do not bother implementing the interface. Creating a ModelLocator class is generally the same as creating any other class in Flex, except that you are responsible for implementing the singleton design pattern. The ModelLocator class(es) are created in the model package, and many developers use the naming convention of prefixing the word model or locator with the name of the application. Once the ModelLocator class has been created, you simply add public properties to it representing the data that needs to be shared. In many cases the properties on the ModelLocator store instances of one of the mx.collections classes or instances of value object classes, but they can be of any type of data. Views subscribe to be notified of changes in the ModelLocator class using data binding. Views obtain references to the properties on the ModelLocator by retrieving the single instance of the ModelLocator using the static getInstance method and then referencing the public properties on the ModelLocator.
53
The FrontController In this chapter you’ll take a detailed look at the Cairngorm FrontController component of the Cairngorm framework. Portions of the code in this chapter are from the Cairngorm SVN repository located at http://opensource.adobe.com/svn/opensource/cairngorm. The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/ wiki/display/cairngorm/license, and is also referenced in the Open Source Notifications section of the introduction to this book.
What Is It? The FrontController is one of the core singleton classes of the Cairngorm framework. According to the article “Flex 3: Introducing Cairngorm” (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles), the FrontController has the following characteristics: ❑
It intercepts dispatched business events and forwards each event instance to the appropriate command instance for processing.
❑
It serves as a registry of event-to-command mappings of Cairngorm event and command classes.
The FrontController essentially acts as a central processing unit for responding to events that need some sort of processing to occur (or as the authors of the article refer to them, business events). This may mean that a remote service needs to be accessed to update or retrieve data or that the ModelLocator class simply needs to be updated based upon a user action. The singleton design allows these events to be handled in a centralized location rather than being scattered across the application.
Chapter 5: The FrontController
What Does It Look Like? The FrontController class is located in the com.adobe.cairngorm.control package. The code for this class looks as follows: package com.adobe.cairngorm.control { import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; import com.adobe.cairngorm.commands.ICommand; import flash.utils.Dictionary; import flash.utils.describeType; import flash.utils.getQualifiedClassName; public class FrontController { protected var commands : Dictionary = new Dictionary(); public function addCommand( commandName : String, commandRef : Class, useWeakReference : Boolean = true ) : void { if ( commandName == null ) throw new CairngormError( CairngormMessageCodes.COMMAND_NAME_NULL ); if ( commandRef == null ) throw new CairngormError( CairngormMessageCodes.COMMAND_REF_NULL ); if( commands[ commandName ] != null ) throw new CairngormError( CairngormMessageCodes.COMMAND_ALREADY_ REGISTERED, commandName ); if ( implementsICommand( commandRef ) == false ) throw new CairngormError( CairngormMessageCodes.COMMAND_SHOULD_IMPLEMENT_ICOMMAND, commandRef ); commands[ commandName ] = commandRef; CairngormEventDispatcher.getInstance().addEventListener( commandName, executeCommand, false, 0, useWeakReference ); } public function removeCommand( commandName : String ) : void { if ( commandName === null ) throw new CairngormError( CairngormMessageCodes.COMMAND_NAME_NULL, commandName); if ( commands[ commandName ] === undefined ) throw new CairngormError( CairngormMessageCodes.COMMAND_NOT_ REGISTERED, commandName); CairngormEventDispatcher.getInstance().removeEventListener( commandName, executeCommand ); commands[ commandName ] = undefined; delete commands[ commandName ]; } protected function executeCommand( event : CairngormEvent ) : void { var commandToInitialise : Class = getCommand( event.type ); var commandToExecute : ICommand = new commandToInitialise(); commandToExecute.execute( event ); } protected function getCommand( commandName : String ) : Class
56
Chapter 5: The FrontController { var command : Class = commands[ commandName ]; if ( command == null ) throw new CairngormError( CairngormMessageCodes.COMMAND_NOT_FOUND, commandName ); return command; } private function implementsICommand( commandRef : Class ) : Boolean { var classDescription : XML = describeType( commandRef ) as XML; return classDescription.factory.implementsInterface.( @type == getQualifiedClassName( ICommand ) ).length() != 0; } } }
References to the command instances are stored in a Dictionary object represented by the protected commands variable. The addCommand function allows event-command pairings to be registered on the FrontController by taking in the event type as a string and a command class to be executed when that type of event is triggered. The removeCommand function removes a command from the list of registered commands. Note here that the value you are using to remove a command is actually the type of event that the command was registered with, not the name of the actual command class, since the event type serves as the dictionary key that references the command class. The protected function executeCommand locates the appropriate command using the type attribute of the passed-in Cairngorm event. It does so by calling the protected getCommand function, which retrieves the command class from the command dictionary property. Once the command has been located, a new instance of the command class is created, and the command’s execute function (which you know it will have, because you have implemented the command design pattern), is called. The private function implementsICommand is used by the addCommand function to determine if the command class being registered implements the ICommand interface. If it does not, an error is thrown. This keeps you from inadvertently adding classes that do not implement the ICommand interface.
How Do You Create it? You create an application-specific FrontController by extending the com.adobe.cairngorm.control .FrontController class. The application-specific FrontController is the class that you will use to register the event and command classes for the application you are currently building. The location of the FrontController will vary depending on what example you are looking at. For instance, the FStop application example from the “Flex 3: Introducing Cairngorm” article creates it in the business package, as shown in Figure 5-1.
57
Chapter 5: The FrontController
Figure 5-1
However, the Cairngorm store example creates it in the control package, as shown in Figure 5-2.
Figure 5-2
For the purposes of creating an example FrontController, we’ll use the business package. To create a FrontController class, start as you would in creating any new class, but stop when the New ActionScript Class dialog box appears. Again, many developers will create the FrontController class by prefixing the word controller with the name of the application or some other descriptive label, as shown in Figure 5-3.
58
Chapter 5: The FrontController
Figure 5-3
To set the superclass to the com.adobe.cairngorm.control.FrontController class, either type the package and class name in the Superclass field, or click the Browse button and select the FrontController from the Open Type dialog box, as shown in Figure 5-4.
Figure 5-4
59
Chapter 5: The FrontController Once you have set the superclass, make sure the “Generate constructor from superclass” box is checked and click Finish. The resulting class should look as follows: package com.cairngormexample.business { import com.adobe.cairngorm.control.FrontController; public class CairngormExampleController extends FrontController { public function CairngormExampleController() { super(); } } }
How Do You Use It? In order for the FrontController class to be able to capture dispatched Cairngorm events, there needs to be an instance of it in the application, normally in the main application file. To include the FrontController class you created, create a namespace in the main application tag pointing to the location where you created the class. Then simply add an instance of it to the main application, as in the following:
Once you have this instance in the application, you then need to register your event-command pairings in the FrontController class that you created. You do this by using the addCommand function, as can be seen in the ShopController class from the Cairngorm store application: package com.adobe.cairngorm.samples.store.control { import com.adobe.cairngorm.control.FrontController; import com.adobe.cairngorm.samples.store.command.* import com.adobe.cairngorm.samples.store.event.UpdateShoppingCartEvent; import com.adobe.cairngorm.samples.store.event.FilterProductsEvent; import com.adobe.cairngorm.samples.store.event.GetProductsEvent;; import com.adobe.cairngorm.samples.store.event.SortProductsEvent; import com.adobe.cairngorm.samples.store.event.ValidateOrderEvent; import com.adobe.cairngorm.samples.store.event.ValidateCreditCardEvent; import com.adobe.cairngorm.samples.store.event.PurchaseCompleteEvent; public class ShopController extends FrontController { public function ShopController() { initialiseCommands(); }
60
Chapter 5: The FrontController public function initialiseCommands() : void { addCommand( GetProductsEvent.EVENT_GET_PRODUCTS, GetProductsCommand ); addCommand( UpdateShoppingCartEvent.EVENT_ADD_PRODUCT_TO_SHOPPING_CART, AddProductToShoppingCartCommand ); addCommand( UpdateShoppingCartEvent.EVENT_DELETE_PRODUCT_FROM_SHOPPING_CART, DeleteProductFromShoppingCartCommand ); addCommand( FilterProductsEvent.EVENT_FILTER_PRODUCTS, FilterProductsCommand ); addCommand( SortProductsEvent.EVENT_SORT_PRODUCTS, SortProductsCommand ); addCommand( ValidateOrderEvent.EVENT_VALIDATE_ORDER, ValidateOrderCommand ); addCommand( ValidateCreditCardEvent.EVENT_VALIDATE_CREDIT_CARD, ValidateCreditCardCommand ); addCommand( PurchaseCompleteEvent.EVENT_COMPLETE_PURCHASE, Complete PurchaseCommand ); } } }
Note that the strings representing the event-type parameter expected by the addCommand function are represented by constants defined on the event classes themselves. The location of the addCommand calls may vary. In the preceding example the constructor calls a function named initializeCommands, which registers the event-command pairings. In other cases you may see them registered directly in the constructor, as in the following: package business { import business.commands.AddItemToCartCommand; import business.commands.LoadItemsCommand; import business.events.AddItemToCartEvent; import business.events.LoadItemsEvent; import com.adobe.cairngorm.control.FrontController; public class ExampleController extends FrontController { public function ExampleControllerController() { super(); addCommand(LoadItemsEvent.EVENT_ID,LoadItemsCommand); addCommand(AddItemToCartEvent.EVENT_ID,AddItemToCartCommand); } } }
Regardless of what function registers the event-command pairings, you need to import the event and command classes, and the addCommand function is passed the event type and command class. Also note that event types are typically stored as constants on the event classes, as they are with standard Flex events.
61
Chapter 5: The FrontController Once the event-command pairings have been registered, you do not need to refer to the instance of the FrontController class. The details of doing so are taken care of for you by the dispatch method defined in the Cairngorm event class. Triggering a given command is simply a matter of dispatching the expected event type, as in the following: var event:LoadItemsEvent=new LoadItemsEvent(); event.dispatch();
Summary In this chapter you looked at the Cairngorm FrontController class. The FrontController has the following characteristics: ❑
It intercepts dispatched business events and forwards each event instance to the appropriate command instance for processing.
❑
It serves as a registry of event-to-command mappings of Cairngorm event and command classes.
The location of the FrontController class in a Cairngorm application may vary, but is typically found in either the business package (as in the FStop application ) or the control package (as in the Cairngorm store example). You create a specific FrontController class (e.g., CairngormExampleController) by extending the com .adobe.cairngorm.control.FrontController class. An instance of the FrontController class must be included in the application so that Cairngorm events can dispatch to it. You add event-command pairings to the FrontController by calling the addCommand function and passing it the event type and command class. You do not need to directly reference the instance of the FrontController class, as the details of doing so are taken care of for you when you call the dispatch method of a Cairngorm event class.
62
Events In this chapter you’ll take a detailed look at the Cairngorm classes related to Cairngorm events. Portions of the code in this chapter are from the Cairngorm SVN repository located at http:// opensource.adobe.com/svn/opensource/cairngorm.The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/wiki/display/ cairngorm/license, and is also referenced in the Open Source Notifications section of the introduction to this book.
What Are They? Cairngorm events are the mechanism through which views communicate to the control layer. Cairngorm events are not all that different from regular Flex events; in fact, both are based on the flash.events.Event class. The major difference between them is that Cairngorm events are self-dispatching and are designed to work with the FrontController class through another class called the CairngormEventDispatcher. Cairngorm events are the mechanism through which your application triggers any logic implemented in the Cairngorm framework.
What Do They Look Like? The classes associated with Cairngorm events are located in the com.adobe.cairngorm.control package. The following classes are contained in this package: ❑
CairngormEvent: Used to differentiate Cairngorm events from events raised by the underlying Flex framework (or similar)
Chapter 6: Events ❑
CairngormEventDispatcher: A singleton class used by the application developer to broadcast events that correspond to user gestures and requests
The CairngormEvent class is the class that the events you create will extend. The code for this class is as follows: package com.adobe.cairngorm.control { import flash.events.Event; public class CairngormEvent extends Event { private var _data : *; public function CairngormEvent( type : String, bubbles : Boolean = false, cancelable : Boolean = false ) { super( type, bubbles, cancelable ); } public function dispatch() : Boolean { return CairngormEventDispatcher.getInstance().dispatchEvent( this ); } public function get data() : * { return _data; } public function set data( value : * ) : void { _data = value; } } }
Cairngorm events extend the standard flash.events.Event class but contain additional methods for interacting with the FrontController class. The CairngormEvent class contains a single private property, _data, with associated getter and setter functions. This property has been provided as a method of passing data on a Cairngorm event when the developer does not want to subclass the CairngormEvent class. The dispatch function is responsible for communicating with the FrontController via the CairngormEventDispatcher singleton class. The code for the CairngormEventDispatcher class is as follows: package com.adobe.cairngorm.control { import flash.events.IEventDispatcher; import flash.events.EventDispatcher; public class CairngormEventDispatcher { private static var instance : CairngormEventDispatcher; private var eventDispatcher : IEventDispatcher; public static function getInstance() : CairngormEventDispatcher
64
Chapter 6: Events { if ( instance == null ) instance = new CairngormEventDispatcher(); return instance; } public function CairngormEventDispatcher( target:IEventDispatcher = null ) { eventDispatcher = new EventDispatcher( target ); } public function addEventListener( type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false ) : void { eventDispatcher.addEventListener( type, listener, useCapture, priority, useWeakReference ); } public function removeEventListener( type:String, listener:Function, useCapture:Boolean = false ) : void { eventDispatcher.removeEventListener( type, listener, useCapture ); } public function dispatchEvent( event:CairngormEvent ) : Boolean { return eventDispatcher.dispatchEvent( event ); } public function hasEventListener( type:String ) : Boolean { return eventDispatcher.hasEventListener( type ); } public function willTrigger(type:String) : Boolean { return eventDispatcher.willTrigger( type ); } } }
You can again see the singleton implementation with the private static instance variable and the getInstance method. The class defines a private variable called eventDispatcher, which holds an instance of an IEventDispatcher. You initialize this instance in the constructor by creating a new EventDispatcher instance. Since the CairngormEventDispatcher class is a singleton, there will always be just one instance of the eventDispatcher. The addEventListener, removeEventListener, hasEventListener, willTrigger, and dispatchEvent functions all simply trigger the corresponding functions on the IEventDispatcher held in the eventDispatcher variable. Since the addCommand function of the FrontController uses the addEventListener function of the CairngormEventDispatcher class to register the event and command pairings, the FrontController becomes the sole listener on the sole publisher represented by the eventDispatcher variable. So any time an event is broadcast via the CairngormEventDispatcher dispatch method, the dispatchEvent function is called on the eventDispatcher, which notifies the FrontController.
65
Chapter 6: Events
How Do You Create One? You create events by extending the com.adobe.cairngorm.control.Event class. Again, depending on what example you are looking at, the location for creating events will vary. The FStop application from the article “Flex 3: Introducing Cairngorm” (cited in previous chapters) creates events in the business.events package, as shown in Figure 6-1.
Figure 6-1
The Cairngorm store creates events in an event package, as shown in Figure 6-2.
Figure 6-2
For the purposes of creating an example event, we’ll use the business.events package. To create an event class, start as you would in creating any new class, but stop when the New ActionScript Class dialog box appears. The naming convention for events is to prefix the word event with something descriptive about the event. (You can see numerous examples of common names in Figure 6-2.) Once you
66
Chapter 6: Events have entered a name for your event, either enter the full package and class name, com.adobe.cairngorm .control.Event, in the Superclass field (Figure 6-3), or use the Browse button and select CairngormEvent from the Open Type dialog (Figure 6-4).
Figure 6-3
Figure 6-4
67
Chapter 6: Events The resulting class should look something like the following: package com.cairngormexample.business.events { import com.adobe.cairngorm.control.CairngormEvent; public class ExampleEvent extends CairngormEvent { public function ExampleEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); } } }
You can construct and use events in a number of ways. These will be covered in detail in later chapters. However, in terms of creating event classes you should be aware of a couple of points, since the way you choose to use events will have an impact on how you construct them. It’s standard practice to store event types as public static constants on the event classes, as in the following: package com.cairngormexample.business.events { import com.adobe.cairngorm.control.CairngormEvent; public class ExampleEvent extends CairngormEvent { public static const EXAMPLE_TYPE:String = ‘exampleEventType’; public function ExampleEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); } } }
It is also a common practice to have a one-to-one correspondence between events and commands. When this is the case, you will have only one type for each event class. Therefore you can modify the event so that you don’t have to pass the type to the constructor, since it will always be the same. This modification looks as follows: package com.cairngormexample.business.events { import com.adobe.cairngorm.control.CairngormEvent; public class ExampleEvent extends CairngormEvent { public static const EXAMPLE_TYPE:String = ‘exampleEventType’; public function ExampleEvent(bubbles:Boolean=false, cancelable:Boolean=false) {
68
Chapter 6: Events super(EXAMPLE_TYPE, bubbles, cancelable); } } }
In the preceding code, the type parameter has been removed and the super-constructor is passed the class constant representing the event type. Events are also responsible for passing along any data that is needed by the command the events will trigger. The CairngormEvent class has the general-purpose data property that can be used to pass data along, but it is also a common practice to use value objects to pass data around the application. Or you can create specific properties on the event for the data that needs to be transferred, since doing so will enable you to have predictable data types. Data that needs to be accessible to classes using the event is stored in public properties, as in the following, which uses an example value object: package com.cairngormexample.business.events { import com.adobe.cairngorm.control.CairngormEvent; import com.cairngormexample.vo.SomeVO; public class ExampleEvent extends CairngormEvent { public static const EXAMPLE_TYPE:String = ‘exampleEventType’; public var neededData:SomeVO; public function ExampleEvent(dataYouNeed:SomeVo,bubbles:Boolean=false, cancelable:Boolean=false) { super(EXAMPLE_TYPE, bubbles, cancelable); this.neededData = dataYouNeed; } } }
In this example the variable neededData holds an instance of the SomeVO value object class. The constructor now takes in an instance of SomeVO and stores it in the neededData instance.
How Do You Use Them? The most common way events are used is in views, to announce some sort of user gesture. As discussed in Chapter 5, before an event will do anything, it must be registered in the FrontController with a corresponding command class. You register events using the addCommand function of the FrontController, as in the following: addCommand(ExampleEvent.EXAMPLE_TYPE, ExampleCommand);
Once the event and command pairing has been registered, you are free to use the event in your application.
69
Chapter 6: Events In Cairngorm 2.1 events can be dispatched differently than in previous versions. Each event now has its own dispatch function. So you can now do something like the following: //event where no data needs to be passed private function initApp():void { var event:LoadItemsEvent=new LoadItemsEvent(); event.dispatch(); } //event where data is being passed private function itemSelectedHandler(event:ItemEvent):void { var addEvent:AddItemToCartEvent= new AddItemToCartEvent(event.selectedItem); addEvent.dispatch(); }
In older versions of Cairngorm, you had to directly call the CairngormEventDispatcher dispatchEvent function, as can be seen in the following function, taken from the ShippingInformation.mxml view from the CairngormStore: private function updateShippingOptions( cost : Number ) : void{ var event : UpdateShippingCostEvent = new UpdateShippingCostEvent(); event.cost = cost; CairngormEventDispatcher.getInstance().dispatchEvent( event ); }
You no longer have to do this, but you may still run across older projects in which events are being dispatched this way.
Summary In this chapter you looked at the classes associated with Cairngorm events. These included: ❑
CairngormEvent: Used to differentiate Cairngorm events from events raised by the underlying Flex framework (or similar)
❑
CairngormEventDispatcher: A singleton class used by the application developer to broadcast events that correspond to user gestures and requests
Cairngorm events and Flex events are both based on the flash.events.Event class. The major difference is that Cairngorm events are self-dispatching and are designed to work with the FrontController class through the CairngormEventDispatcher. You create a Cairngorm event by extending the com.adobe.cairngorm.control.CairngormEvent class. Depending on the example you are looking at, they may be created in different locations, including the business.events package (as in the FStop application from the “Flex 3: Introducing Cairngorm” article) or an events package (Cairngorm store).
70
Chapter 6: Events The general naming convention is to prefix the word event with something descriptive about the logic that the event triggers (e.g., LoadItemsEvent). If you are creating events using the one-to-one correspondence with commands, your event will have only one type, and you can modify the constructor to remove the type parameter and pass the superconstructor the type constant. This saves you from having to specify the type when you use the event class. While the CairngormEvent class does provide the data property for transferring data on the event object, many people prefer to have predictable types and create their own public properties on the event to transfer data. In many cases, such a property may be an instance of a value object class. You must use the addCommand function to register an event in the FrontController with a corresponding command class before the event will do anything. Using an event is a matter of dispatching it. In Cairngorm 2.1 the events are self-dispatching through the dispatch method. Older versions of Cairngorm required you to use the CairngormEventDispatcher singleton class. You may still find projects with code that dispatches events in this manner.
71
Commands In this chapter you’ll take a detailed look at the Cairngorm classes related to Cairngorm commands. Portions of the code in this chapter are from the Cairngorm SVN repository located at http:// opensource.adobe.com/svn/opensource/cairngorm. The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/wiki/display/ cairngorm/license, and is also referenced in the Open Source Notifications section of the introduction to this book.
What Are They? Commands are the classes that determine how the event that triggers them should be handled. According to the article “Flex 3: Introducing Cairngorm” (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles), the commands have the following characteristics: ❑
Each command class represents a specific business feature with associated business logic and processing.
❑
Commands update the ModelLocator with new data or changes to existing data.
❑
All commands have the same entry point to initiate or start business processing: execute().
❑
Command implementations have class names that are equivalent to business events (e.g., LoadPhotosEvent and LoadPhotosCommand).
Commands classes decide how Cairngorm events are handled. When a Cairngorm event is dispatched, the FrontController calls the execute function on the command class that is registered for the event.
Chapter 7: Commands The execute function contains the logic that needs to be executed. This may include accessing remote services using a delegate class or it may simply update the ModelLocator. Regardless of whether services are used or the ModelLocator is simply being updated, command classes are the only classes that should be updating data on the ModelLocator.
What Do They Look Like? The classes and interfaces associated with the Cairngorm commands are located in the com.adobe .cairngorm.commands package. This package contains the following classes and interfaces: ❑
❑
Interfaces ❏
Command: Enforces the contract between the FrontController and concrete command classes in your application. Deprecated as of Cairngorm 2.1 and replaced by com .adobe.cairngorm.commands.ICommand
❏
ICommand: Enforces the contract between the FrontController and concrete command classes in your application
Classes ❏
SequenceCommand: Provided as a “pseudo-abstract” (since ActionScript has no real concept of abstract classes) base class that can be extended when you wish to chain commands together for a single user gesture, or establish some simple form of decisionbased workflow
The Command interface has been deprecated in favor of the ICommand interface. The code for the ICommand interface is as follows: package com.adobe.cairngorm.commands { import com.adobe.cairngorm.control.CairngormEvent; public interface ICommand { function execute( event : CairngormEvent ) : void; } }
This is the interface responsible for enforcing the implementation of the command design pattern. It defines a single method, execute, that takes in a CairngormEvent class. The SequenceCommand class is more or less a utility class that enables you to chain calls of commands together. The code for this class is as follows: package com.adobe.cairngorm.commands { import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.control.CairngormEventDispatcher; public class SequenceCommand implements ICommand { public var nextEvent : CairngormEvent;
74
Chapter 7: Commands public function SequenceCommand( nextEvent : CairngormEvent = null ) : void { super(); this.nextEvent = nextEvent; } public function execute( event : CairngormEvent ) : void { /* * abstract, so this method must be provided. * Rather than convolute additional framework classes to enforce * abstract classes, we instead delegate responsibility to the * developer of a SequenceCommand to ensure that * they provide a concrete implementation of this method. */ } public function executeNextCommand() : void { var isSequenceCommand : Boolean = ( nextEvent != null ); if( isSequenceCommand ) CairngormEventDispatcher.getInstance().dispatchEvent( nextEvent ); } } }
You can see that this class itself implements the ICommand interface. In addition to the execute function defined in the ICommand interface, this class contains public variable nextEvent and a function named executeNextCommand. The nextEvent holds the next CairngormEvent to be triggered in the sequence, and the executeNextCommand function dispatches this event using the CairngormEventDispatcher. By using several SequenceCommand objects chained together, you can create a sequence of logic that is triggered by a single CairngormEvent in which each subsequent command is called only if the previous one succeeds. You will see examples of using this class in Chapter 24 of this book. In addition to implementing the ICommand interface, it’s also common for command classes to implement a Responder interface when they are responsible for contacting remote services. In Cairngorm 2.1 the com.adobe.cairngorm.business.Responder has been replaced with the mx.rpc .IResponder interface, which is part of the standard Flex framework (http://livedocs.adobe.com/ flex/3/langref/mx/rpc/IResponder.html).
The basic idea is the same. The mx.rpc.IResponder interface defines two functions: ❑
fault(info:Object):void: This method is called by a service when an error has been received.
❑
result(data:Object):void: This method is called by a service when the return value has been received.
75
Chapter 7: Commands These functions correspond to the events triggered by the remoting classes stored in the ServiceLocator. Here is an example of a command class implementing this interface: package com.cairngormexample.commands { import com.cairngormexample.business.events.LoginEvent; import com.cairngormexample.business.ServiceLocator; import com.cairngormexample.models.CairngormExampleModel; import com.adobe.cairngorm.commands.ICommand; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; import mx.rpc.AsyncToken public class LoginCommand implements ICommand, IResponder { private var model: CairngormExampleModel = CairngormExampleModel.getInstance(); public function execute(event:CairngormEvent):void { var evt:LoginEvent = event as LoginEvent; var service:HTTPService = ServiceLocator.getInstacne().getHTTPService(‘loginService’); var token = service.send(evt); token.addResponder(this); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; //do something with results } public function fault(info:Object):void { var evt:FaultEvent = info as FaultEvent; } } }
The implementation of the IResponder interface is only necessary if the command needs to respond to a remote call.
How Do You Create One? You create commands by implementing the ICommand interface, and if necessary the IResponder interface. Commands are generally created in a command package and the naming convention is to prefix the word command with something descriptive about what the command does. The following example creates a command that implements both the ICommand and IResponder interfaces. When creating such a command, there are a couple of things that you want to do in the New ActionScript Class dialog shown in Figure 7-1. (Make sure the “Generate functions inherited from interfaces” box is checked.)
76
Chapter 7: Commands
Figure 7-1
First, click the Add button next to the Interfaces field. In the Open Type dialog select the ICommand interface, as in Figure 7-2.
Figure 7-2
Repeat these steps and select the IResponder interfaces, as in Figure 7-3.
77
Chapter 7: Commands
Figure 7-3
The resulting class should look something like the following: package com.cairngormexample.commands { import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import com.adobe.cairngorm.commands.ICommand; public class ExampleCommand implements ICommand, IResponder { public function ExampleCommand(){} public function execute(event:CairngormEvent):void { } public function result(data:Object):void { } public function fault(info:Object):void { } } }
How Do You Use Them? Command classes decide how Cairngorm events are handled. Before a command will do anything, it must be registered in the FrontController with a corresponding event. You do this using the addCommand function of the FrontController, as in the following: addCommand(ExampleEvent.EXAMPLE_TYPE, ExampleCommand);
78
Chapter 7: Commands In most cases your command classes will be updating the model, often in response to some call to the server. An example of this can be seen in the GetProductsCommand from the Cairngorm web store: package com.adobe.cairngorm.samples.store.command { import mx.rpc.IResponder; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.samples.store.business.ProductDelegate; import com.adobe.cairngorm.samples.store.model.ShopModelLocator; import com.adobe.cairngorm.samples.store.util.Comparator; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; import mx.controls.Alert; import mx.collections.ICollectionView; import mx.collections.Sort; import mx.collections.SortField; import mx.utils.ArrayUtil; public class GetProductsCommand implements ICommand, IResponder { public function GetProductsCommand(){} public function execute( event : CairngormEvent ): void { if( ShopModelLocator.getInstance().products == null ) { var delegate : ProductDelegate = new ProductDelegate( this ); delegate.getProducts(); } else { Alert.show( “Products already retrieved!” ); return; } } public function result( event : Object ) : void { var products : ICollectionView = ICollectionView( event.result ); var model : ShopModelLocator = ShopModelLocator.getInstance(); // sort the data var sort :Sort = new Sort(); sort.fields = [ new SortField( “name”, true ) ]; products.sort = sort; products.refresh(); // set the products on the model model.selectedItem = products[ 0 ]; model.products = products; model.workflowState = ShopModelLocator.VIEWING_PRODUCTS_IN_THUMBNAILS; } public function fault( event : Object ) : void { var faultEvent : FaultEvent = FaultEvent( event ); Alert.show( “Products could not be retrieved!” ); } } }
79
Chapter 7: Commands The execute function of command classes is automatically triggered when the dispatch method of the corresponding event registered in the FrontController is called.
Summary In this chapter you looked at the classes and interfaces associated with Cairngorm commands. These included: ❑
❑
Interfaces ❏
Command: Enforces the contract between the FrontController and concrete command classes in your application. Deprecated as of Cairngorm 2.1 and replaced by com.adobe .cairngorm.commands.ICommand.
❏
ICommand: The ICommand interface enforces the contract between the FrontController and concrete command classes in your application.
Classes ❏
SequenceCommand: The SequenceCommand is provided as a “pseudo-abstract” base class (since ActionScript has no real concept of abstract classes), which can be extended when you wish to chain commands together for a single user gesture, or establish some simple form of decision-based workflow.
Cairngorm commands have the following qualities: ❑
Each command class represents a specific business feature with associated business logic and processing.
❑
Commands update the ModelLocator with new data or changes to existing data.
❑
All commands have the same entry point to initiate or start business processing: execute().
❑
Command implementations have class names that are equivalent to business events (e.g., LoadPhotosEvent and LoadPhotosCommand).
Cairngorm command classes are generally created in a command package. The general naming convention is to prefix the word command with something descriptive about what the command does. It is also a convention that the prefix used for the command class and the prefix used for the corresponding event class match. You create commands by implementing the com.adobe.cairngorm.commands.ICommand interfaces, and the mx.rpc.IResponder interface if the command needs to respond to a server call. Commands must be registered in the FrontController with a corresponding event type by means of the addCommand function before they will do anything. The execute function of the command class is automatically triggered when the dispatch function of the corresponding event is called.
80
Delegates In this chapter you’ll take a detailed look at how delegate classes are used in the Cairngorm framework. Portions of the code in this chapter are from the Cairngorm SVN repository located at http:// opensource.adobe.com/svn/opensource/cairngorm.The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/wiki/display/ cairngorm/license, and is also referenced in the Open Source Notifications section of the introduction to this book.
What Are They? Delegates are classes that are used as remote proxies. According to the article referred to earlier in this book, “Flex 3: Introducing Cairngorm” (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles), delegates have the following characteristics: ❑
Each delegate provides a local proxy for a remote service.
❑
Each delegate serves as a contract between client and server development teams.
❑
They may hide underlying implementation details, including: ❏
Client-side service lookups
❏
Server API method invocations
❏
Formatting of method arguments
❏
Data transformations
❑
Delegates allow for mock services and dummy data to be used while server implementations are resolved.
❑
They are used in the control layer and should not be used outside it.
Chapter 8: Delegates Delegate classes act as proxies for accessing remote services. It is the delegate class that performs the lookup of a service using the ServiceLocator. Delegates are used in command classes that need to update or retrieve remote data. Since the delegate class handles the details of accessing the service, the command class only needs to know what function to call on the delegate and what type of data the service call is going to return. In addition to hiding the details of service calls, delegates generally group related sets of service calls together. For example, a UserDelegate might contain functions for login, logout, and registering. Delegates can also be used as stand-ins for live services that are still being developed, by providing mock data that gets returned until the live service is ready. This allows you to create the rest of your classes as you would normally. Then, when the server side is ready, all you have to do is modify the delegate classes to take the system live.
What Do They Look Like? There are no classes or interfaces for delegates in the Cairngorm framework. Delegates are mostly used as a convention. However, delegate classes generally do have predictable structures. The constructor of a delegate allows for the specification of an IResponder class that will handle the results of any remote calls. Each additional method on the delegate represents a particular type of action that can be performed using the service. An example delegate might look like the following: package delegates { import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.AsyncToken; import mx.rpc.IResponder; import mx.rpc.http.HTTPService; public class ProductsDelegate { private var locator:ServiceLocator=ServiceLocator.getInstance(); private var service:HTTPService; private var responder:IResponder; public function ProuctsDelegate(responder:IResponder) { this.service = this.locator.getHTTPService(“productsService”); this.responder = responder; } public function loadProducts():void { var token:AsyncToken = this.service.send(); token.addResponder(__responder); } } }
82
Chapter 8: Delegates You may recall that in the last chapter, on commands, it was stated that when commands need to access remote data, they implement the IResponder interface. In most cases it’s the command class that is passed to the delegate constructor. The passed-in IResponder is stored in the responder instance variable. The delegate constructor function also looks up the appropriate service in the constructor and stores that in the service instance variable. When the loadProducts function of the delegate is called, the send method of the service held in the service instance variable is triggered, and the IResponder held in the responder instance variable is added as a responder to the service call via the AsyncToken returned by the service send method. This same basic interface and logic holds true regardless of what type of service you are using, as can be seen in the ProductDelegate from the Cairngorm store: package com.adobe.cairngorm.samples.store.business { import mx.rpc.IResponder; import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import mx.rpc.AbstractOperation; public class ProductDelegate { public function ProductDelegate( responder : IResponder ) { this.service = ServiceLocator.getInstance().getRemoteObject ( “productService” ); this.responder = responder; } public function getProducts() : void { var call : Object = service.getProducts(); call.addResponder( responder ); } private var responder : IResponder; private var service : Object; } }
This class uses a RemoteObject, but as you can see, other than the service being retrieved from the ServiceLocator, the rest is essentially the same as the previous example.
How Do You Create One? The location of delegate classes will vary according to the example you look at. “Flex 3: Introducing Cairngorm” (cited in earlier chapters) creates them in a business.delegates package, as shown in Figure 8-1.
83
Chapter 8: Delegates
Figure 8-1
The Cairngorm store, on the other hand, creates them in the business package, as shown in Figure 8-2.
Figure 8-2
For demonstration purposes the following example will be created directly in the business package. Since delegates do not extend any Cairngorm classes or implement any interfaces, creating one is just like creating any other ActionScript class in Flex. The naming convention is to prefix the word delegate with something descriptive about the type of services the delegate handles (e.g., ProductDelegate). Once you have the basic class created, you simply need to add the code and function that will enable you to access the services, as in the following: package com.cairngormexample.business { import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.AsyncToken; import mx.rpc.IResponder; import mx.rpc.http.HTTPService; public class ExampleDelegate { private var responder:IResponder; public function ExampleDelegate(responder:IResponder) { this.responder = responder; } public function loadSomeData():void{
84
Chapter 8: Delegates var service:HTTPService = ServiceLocator.getInstance().getHTTPService(‘exampleService’); var token:AsyncToken = service.send(); token.addResponder(this.responder); } } }
A private instance variable responder holds the instance of the IResponder that will handle the results of the service call passed in via the constructor. Creating the rest of the delegate is simply a matter of adding the functions that will contact the various services and register the responder held in the responder instance variable of the delegate as a responder for the service call.
How Do You Use Them? Delegates are used in command classes when they need to access a remote service. An example of this can be seen in the GetProductsCommand from the Cairngorm web store: package com.adobe.cairngorm.samples.store.command { import mx.rpc.IResponder; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.samples.store.business.ProductDelegate; import com.adobe.cairngorm.samples.store.model.ShopModelLocator; import com.adobe.cairngorm.samples.store.util.Comparator; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; import mx.controls.Alert; import mx.collections.ICollectionView; import mx.collections.Sort; import mx.collections.SortField; import mx.utils.ArrayUtil; public class GetProductsCommand implements ICommand, IResponder { public function GetProductsCommand() { } public function execute( event : CairngormEvent ): void { if( ShopModelLocator.getInstance().products == null ) { var delegate : ProductDelegate = new ProductDelegate( this ); delegate.getProducts(); } else { Alert.show( “Products already retrieved!” );
85
Chapter 8: Delegates return; } } public function result( event : Object ) : void { var products : ICollectionView = ICollectionView( event.result ); var model : ShopModelLocator = ShopModelLocator.getInstance(); // sort the data var sort :Sort = new Sort(); sort.fields = [ new SortField( “name”, true ) ]; products.sort = sort; products.refresh(); // set the products on the model model.selectedItem = products[ 0 ]; model.products = products; model.workflowState = ShopModelLocator.VIEWING_PRODUCTS_IN_THUMBNAILS; } public function fault( event : Object ) : void { var faultEvent : FaultEvent = FaultEvent( event ); Alert.show( “Products could not be retrieved!” ); } } }
A new ProductDelegate is created in the execute function and the command class is registered as a responder. Then the getProducts function of the ProductsDelegate is called. If all goes well with the remote call, the result function is called and is passed back any data from the remote service via the ResultEvent. That data is then used to update the ModelLocator. If something goes wrong with the remote call, the fault function is called and is passed a FaultEvent.
Summary In this chapter you looked at delegate classes. Delegates have the following characteristics:
86
❑
Each delegate provides a local proxy for a remote service.
❑
Each delegate serves as a contract between client and server development teams.
❑
They may hide underlying implementation details, including: ❏
Client-side service lookups
❏
Server API method invocations
❏
Formatting of method arguments
❏
Data transformations
Chapter 8: Delegates ❑
Delegates allow for mock services and dummy data to be used while server implementations are resolved.
❑
They should be used only in the Control layer.
No classes or interfaces for delegates are provided in the Cairngorm framework; delegates are simply used as a convention. The location of delegate classes will vary according to the example that you look at, but they are generally found in a business package. The standard naming convention is to prefix the word delegate with something descriptive of the types of services the delegate handles. The constructor of a delegate allows for the specification of an IResponder class that will handle the results of any remote calls. Each additional method on the delegate represents a particular type of action that can be performed using the service.
87
Value Objects In this chapter you’ll take a detailed look at the Cairngorm interfaces related to Cairngorm value objects. Portions of the code in this chapter are from the Cairngorm SVN repository, located at http:// opensource.adobe.com/svn/opensource/cairngorm. The full text of the copyright and conditions of use information can be found at http://opensource.adobe.com/wiki/display/ cairngorm/license, and is also referenced in the “Open Source Notifications” section of the introduction to this book.
What Are They? Value objects (VOs) are also sometimes known as data transfer objects (DTOs). They are used to transfer data between software application subsystems. In addition to passing data within an application, value objects can mirror a server-side object. When this is the case in Flex, you will sometimes see them used with the Flex metadata tag RemoteClass. When the class instance is serialized via Action Message Format (AMF), the RemoteClass metadata tag registers the class with Flex so that Flex preserves type information. This metadata tag can also be used to represent a server-side object in a client application in any server-side language that has an AMF gateway. You use the [RemoteClass(alias=” “)] metadata tag to create an ActionScript object that maps directly to the server-side object. You specify the fully qualified class name of the server-side class as the value of the alias.
Chapter 9: Value Objects
What Do They Look Like? The interfaces associated with value objects are contained in the com.adobe.cairngorm.vo package. The following interfaces are contained in this package: ❑
IValueObject: The IValueObject interface is a marker interface that improves the readability of code by identifying the classes within a Cairngorm application that are to be used as value objects for passing data between tiers of an application.
❑
ValueObject: Deprecated as of Cairngorm 2.1 and replaced by com.adobe.cairngorm.vo .IValueObject.
The code for the IValueObject interface is as follows: package com.adobe.cairngorm.vo { public interface IValueObject { } }
Much like the IModelLocator interface, the IValueObject interface exists simply to mark a class as a value object. Again, since the interface does not define any methods, it is not always implemented in practice. Value objects consist of collections of related public properties. A simple value object might look as follows: package valueObjects { [Bindable] public class UserVO { public var firstName:String; public var lastName:String; public function UserVO() { } } }
An example of a value object that uses the RemoteClass metadata tag can be seen in the ProductVO of the Cairngorm store application: package com.adobe.cairngorm.samples.store.vo { import com.adobe.cairngorm.vo.IValueObject; import com.adobe.cairngorm.samples.store.util.Comparable; [RemoteClass(alias=”com.adobe.cairngorm.samples.store.vo.ProductVO”)] public class ProductVO implements IValueObject, Comparable { public function get identifier() : String {
90
Chapter 9: Value Objects return String( id ); } public function toString() : String { var s : String = “ProductVO[id=”; s += id; s += “, name=”; s += name; s += “, description=”; s += description; s += “, price=”; s += price; s += “, image=”; s += image; s += “,thumbnail=”; s += thumbnail; s += “ ]”; return s; } [Bindable] public var id : Number; [Bindable] public var name : String; [Bindable] public var description : String; [Bindable] public var price : Number; [Bindable] public var image : String; [Bindable] public var thumbnail : String; } }
The corresponding server side class (in this case a Java class) looks as follows: package com.adobe.cairngorm.samples.store.vo; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.ToStringBuilder; public class ProductVO { public int getId() { return id; } public void setId( int id ) { this.id = id; } public String getName() { return name; }
91
Chapter 9: Value Objects public void setName( String name ) { this.name = name; } public String getDescription() { return description; } public void setDescription( String description ) { this.description = description; } public int getDescriptionLength() { return (description == null) ? 0 : description.length(); } public float getPrice() { return price; } public void setPrice( float price ) { this.price = price; } public String getImage() { return image; } public void setImage( String image ) { this.image = image; } public String getThumbnail() { return thumbnail; } public void setThumbnail( String thumbnail ) { this.thumbnail = thumbnail; } public String toString() { return new ToStringBuilder( this ).append( “id“, getId() ).append( “name“, this.getName() ).append( “description.length“, this.getDescriptionLength() ).append( “price“, this.getPrice() ) .append( “image“, this.getImage() ).append( “thumbnail“, this.getThumbnail() ).toString(); } public boolean equals( Object obj ) { if( !(obj instanceof ProductVO) ) { return false;
92
Chapter 9: Value Objects } ProductVO product = (ProductVO) obj; return new EqualsBuilder().append( name, product.name ).append( description, product.description ).append( price, product.price ) .append( image, product.image ).append( thumbnail, product.thumbnail ).isEquals(); } private private private private private private
int id; String name; String description; float price; String image; String thumbnail;
}
How Do You Create One? Again, you will see varying locations for creating value objects, and you will also see varying names for the package that contains the value object classes. “Flex 3: Introducing Cairngorm” (cited in earlier chapters) creates in the valueObjects package, as shown in Figure 9-1.
Figure 9-1
The Cairngorm store creates them in a vo package, as shown in Figure 9-2.
Figure 9-2
93
Chapter 9: Value Objects The general naming convention is to prefix the letters VO with the name of the object represented. Since implementing the IValueObject interface is optional, creating a value object is basically the same as creating any other class in Flex. After you have created the class, you simply add the public properties to it. If you are using the value object as a data transfer object, you need to make sure that these properties match the remote object to which they correspond.
How Do You Use Them? Value objects are used to pass data between layers of the application. Most often this transfer takes the form of passing an instance of a value object being passed between event, command, and if necessary, delegate classes. This can be seen in the UpdateShoppingCartEvent and AddProductToShopping CartCommand classes from the Cairngorm store. The UpdateShoppingCartEvent has a public property product that holds an instance of a ProductVO: package com.adobe.cairngorm.samples.store.event { import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.samples.store.vo.ProductVO; import flash.events.Event; public class UpdateShoppingCartEvent extends CairngormEvent { public static const EVENT_ADD_PRODUCT_TO_SHOPPING_CART : String = “addProductToShoppingCart”; public static const EVENT_DELETE_PRODUCT_FROM_SHOPPING_CART : String = “deleteProductFromShoppingCart”; public var product : ProductVO; public var quantity : Number; public function UpdateShoppingCartEvent( type : String, bubbles : Boolean = true, cancelable : Boolean = false ) { super( type, bubbles, cancelable ); } override public function clone() : Event { return new UpdateShoppingCartEvent( type, bubbles, cancelable ); } } }
This product property is then referenced in the AddProductToShoppingCartCommand via the event, as can be seen in the following: package com.adobe.cairngorm.samples.store.command { import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cairngorm.samples.store.model.ShopModelLocator; import com.adobe.cairngorm.samples.store.event.UpdateShoppingCartEvent; public class AddProductToShoppingCartCommand implements ICommand
94
Chapter 9: Value Objects { public function AddProductToShoppingCartCommand() { } public function execute( event : CairngormEvent ): void { var shoppingEvent : UpdateShoppingCartEvent = UpdateShoppingCartEvent( event ); ShopModelLocator.getInstance().shoppingCart.addElement( shoppingEvent.product, shoppingEvent.quantity ); } } }
The particular product held by the event is set in the view before the event is dispatched, as can be seen in the addProductToShoppingCart function of the ProductDetails view: public function addProductToShoppingCart () : void { var event : UpdateShoppingCartEvent = new UpdateShoppingCartEvent( UpdateShoppingCartEvent.EVENT_ADD_PRODUCT_TO_SHOPPING_CART ); event.product = selectedItem; event.quantity = numericStepperComp.value; CairngormEventDispatcher.getInstance().dispatchEvent( event ); }
Summary In this chapter you looked at the interfaces associated with value objects and the uses of value objects. Value objects (VOs) are also sometimes known as data transfer objects (DTOs). Value objects are used to transfer data between software application subsystems. A value object can mirror a server-side object using the RemoteClass metadata tag, which Flex uses to preserve type information when serializing a class using the Action Message Format (AMF). You can also use this metadata tag to represent a server-side object by specifying the full package of the remote class as the value of the alias in the RemoteClass metadata tag. You may see value objects in a package named vo, valueObjects, or something similar. Regardless of the location, the general naming convention is to prefix the capital letters VO with the name of the object represented (e.g., ProductVO). Using value objects generally involves having a public variable on an event class that is then accessed via the command class and passed to a delegate class if necessary.
95
How the Pieces Work Together In this chapter you take a look at a full cycle of Cairngorm application logic by examining a simple login form.
A Simple Cairngorm Logic Flow Example In this section you look at a simple example of how all the pieces of Cairngorm work together and how they correspond to the logic flow diagram in Figure 10-1. To do this, you will examine a simple login form. Delegate locates service
CairngormEvent dispatched
SeviceLocator returns service
FrontController finds command class and calls execute function Command executes
Yes
Delegate calls service
Accesses remote data?
Command updates ModelLocator ModelLocator updates views via data binding Views updated
Figure 10-1
Backend returns result
Chapter 10: How the Pieces Work Together
View Dispatches Cairngorm Event This step corresponds to the part of the diagram shown in Figure 10-2.
CairngormEvent dispatched
Figure 10-2
The following example login form triggers the initial event that starts the Cairngorm logical flow:
When the user clicks the Login button, a new UserVO is constructed and populated with the values from the form. This UserVO is then passed as an argument to a new LoginEvent. This LoginEvent is then dispatched.
98
Chapter 10: How the Pieces Work Together
FrontController Intercepts Event and Triggers Command This step corresponds to the part of the original diagram reproduced here as Figure 10-3.
FrontController finds command class and calls execute function
Figure 10-3
The LoginEvent.LOGIN event type is registered with a corresponding command in the FrontController, as shown in the following code: package com.CairngormLogin.control { import com.CairngormLogin.commands.LoginCommand; import com.adobe.cairngorm.control.FrontController; public class LoginControl extends FrontController { public function LoginControl() { addCommand( LoginEvent.LOGIN, LoginCommand ); } } }
The FrontController intercepts the dispatched event, finds the command that is registered for the event, and calls the execute function. In this case, the registered command is LoginCommand.
Command Decides How to Handle Event This step corresponds to the part of the original diagram reproduced here as Figure 10-4.
Command executes
Accesses remote data?
Figure 10-4
99
Chapter 10: How the Pieces Work Together The command can do one of several things at this point. It can: ❑
Directly update the ModelLocator
❑
Directly call a service
❑
Call a function on a delegate
In this example, the command will call a function on a delegate in order that part of the system can be explored. Since the command is accessing a remote service, it will implement both the ICommand and IResponder interfaces. The LoginCommand looks as follows: package com.CairngormLogin.commands { import com.CairngormLogin.business.LoginDelegate; import com.CairngormLogin.control.LoginEvent; import com.CairngormLogin.model.CairngormLoginModel; import com.CairngormLogin.vo.UserVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; public class LoginCommand implements ICommand, IResponder { private var model:CairngormLoginModel = CairngormLoginModel.getInstance(); public function execute(event:CairngormEvent):void { var delegate : LoginDelegate = new LoginDelegate( this ); var loginEvent : LoginEvent = LoginEvent( event ); delegate.login( loginEvent.userVO ); } public function result(data:Object):void { var userVO:UserVO = UserVO(data.result.user) var success:Boolean = data.result.success; if(success){ this.model.loginMessage = “Welcome “ + userVO.username +”.”; }else{ this.model.loginMessage = “No account with that username and password could be found.”; } } public function fault(info:Object):void { this.model.loginMessage = “There was a problem communicating with the server.”; } } }
The execute function creates a new LoginDelegate and registers itself as the responder in the delegate constructor.
100
Chapter 10: How the Pieces Work Together The CairngormEvent passed to the execute function is cast as a LoginEvent, and the UserVO contained in the UserVO property of the event is passed to the login function of the delegate.
Delegate Calls Remote Service This step corresponds to the part of the original diagram represented here as Figure 10-5.
Delegate locates service SeviceLocator returns service
Delegate calls service
Backend returns result
Figure 10-5 The LoginDelegate looks as follows: package com.CairngormLogin.business { import com.CairngormLogin.vo.UserVO; import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; import mx.rpc.http.HTTPService; public class LoginDelegate { private var responder :IResponder; private var service : HTTPService; public function LoginDelegate( responder : IResponder ) { this.service = ServiceLocator.getInstance().getHTTPService(‘loginService’); this.responder = responder; } public function login( userVO : UserVO ): void
101
Chapter 10: How the Pieces Work Together { //live system //var token : AsyncToken = service.send( userVO ); //token.addResponder(this.responder);//for simulation; var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false, true); var loginResults:Object = new Object(); loginResults.user = userVO; if (userVO.username == ‘someuser’ && userVO.password == ‘password’){ loginResults.success = true; }else{ loginResults.success = false; } this.responder.result(new ResultEvent(ResultEvent.RESULT,false,true,loginResults)); } } }
The constructor takes the passed in IResponder class, stores it in the responder property, and retrieves the loginService from the ServiceLocator. The login function takes in a UserVO as an argument so it can use the properties on the UserVO to perform the login. In the commented-out code at the top of the login function you can see what the actual service call would look like. Below that is some simulated code that allows the example to be run without a live service. In a live system the command class would simply respond to the service call when it completed, since the command class is registered as a responder. The simulation simply calls the function directly and passes some data that the command can use via the result property of the ResultEvent.
Command Updates ModelLocator This step corresponds to the part of the diagram shown in Figure 10-6.
Command updates ModelLocator
Figure 10-6 If the remote call completes successfully, the result function of the command class is called (since it is the responder). The result function uses the success property of the result object of the ResultEvent to determine if the account was found. In both the account-found and account-not-found cases, the loginMessage property of the ModelLocator is updated to indicate the status of the login attempt. If something should go wrong with the service call, the fault function will be triggered and the loginMessage property of the ModelLocator would be updated with a failure message.
102
Chapter 10: How the Pieces Work Together
Changes in ModelLocator Are Broadcast via Data Binding This step corresponds to the part of the diagram shown in Figure 10-7, and brings the cycle full circle.
ModelLocator updates views via data binding Views updated
Figure 10-7 The following view contains a label that is bound to the loginMessage property of the ModelLocator:
When the command updates the ModelLocator with the status message, the view displays the message to the user, as can be seen in Figures 10-8 (login incorrect) and 10-9 (login correct).
Figure 10-8
103
Chapter 10: How the Pieces Work Together
Figure 10-9
Summary In this chapter you examined how the pieces of Cairngorm work together by examining a simple login application. The basic Cairngorm logic flow and corresponding classes from the sample application can be seen in the table below:
Cairngorm Logic Flow
Sample Application Class(es)
Triggered By
Cairngorm event is broadcast.
LoginEvent, LoginForm view
The user clicks the Login button.
The FrontController intercepts the event, locates the corresponding command, and triggers the execute function.
LoginControl
The Cairngorm event is dispatched.
The command class decides how to handle the event. This may be by simply updating the ModelLocator, or by calling a remote service, perhaps using a delegate.
LoginCommand
The FrontController finds a command class for the event and calls the execute function.
If a delegate is being used, it contacts the service and registers a responder.
LoginDelegate
Implemented by a command class that calls the desired function.
The command class updates the ModelLocator with any data from the remote service, or updates it based on the success of the remote service call.
LoginCommand
The responder functions are triggered when the remote call has completed.
The ModelLocator notifies views of changes via data binding.
CairngormLoginModel
Data binding triggers updating of views.
104
Project Overview In this chapter you are introduced to the sample project that you will build to learn the Cairngorm framework. The application is not a complete one, nor is it intended to be. It has enough features that you will be proficient (or at least comfortable) with the Cairngorm framework after completing it. In the initial build of the application (that is, during implementation of the list of features outlined in the “Project Overview” section of this chapter), you will follow the “typical” method of building an application with Cairngorm. By typical, I mean the one suggested by the creators of Cairngorm. More specifically, I mean having matching command and if necessary delegate classes for each event class (e.g., LoginEvent, LoginCommand, or LoginDelegate). After you have implemented the list of features from this chapter using the typical methodology, we will break from the imaginary client paradigm and explore alternative methods for implementing the features.
Meet the Client You have just taken on a freelance project with a client named Miss Information Media. Miss Information Media is a popular publisher in the print world (you have probably seen some of its publications at your local grocer ’s checkout line — look for the child with the bat ears), and it’s in the process of creating an online presence. Part of the company’s online initiative is a blogging application that will be used by its authors to post articles similar to those found in its print publications. You, along with several other Flex developers, have been hired to work with the in-house Flex developer to complete this blogging application. The in-house Flex developer has mandated that the application be built using the Cairngorm framework. Each developer will be given a list of features that he or she is responsible for.
Chapter 11: Project Overview
Project Over view You are responsible for completing the following portions of the blogging application: ❑
Main application MXML file
❑
User registration
❑
Login and logout
❑
Addition of new posts
❑
Loading of existing posts
❑
Commenting system
❑
Search capabilities
The data for the application is stored in a database with the following tables (you will learn more about the database and how you will access it in Chapter 13). ❑
users: Stores user account information
❑
posts: Stores the actual posts
❑
comments: Stores comments associated with individual posts
❑
categories: Stores the list of categories that posts can belong to
❑
posts_view: A view that combines information from the other tables for ease of selection
You will access the data in this database via delegate classes. You do not need to know the particulars of the database other than what parameters you are expected to send and what information you will get back. In most cases you will be creating value objects that match the database column names so that these objects can be used to pass data to and from the database. (You will learn more about this process in Chapter 13.) The requirements for the features you are responsible for are outlined in the sections that follow.
Main Application The main application is not a feature per se, but since you’ll need to be able to access things like the FrontController, and you need a place to test your classes, you’re going to need to create a shell application to hold these components. Additionally, to create posts in the application will require access to a restricted area. Because of this you will need a view that you can use to test the access restriction in the main application. The marketing department of Miss Information Media is still debating the look and feel of the application, so for the time being it isn’t worried about layout and colors. The interface should simply be usable and demonstrate that the features work. The in-house Flex developer will handle the final layout once all the features have been implemented.
106
Chapter 11: Project Overview
User Registration Miss Information Media wants the registration process to be as quick and simple as possible. You will be collecting only the following information: ❑
First name
❑
Last name
❑
User name
❑
Password
All these fields are required and user names and passwords must be at least five characters. The user must be able to cancel the registration process at any time before submitting the form. Canceling should take the user back to the main post view. Additionally, when the registration process is successful the following should occur: ❑
The registration form should be cleared.
❑
The user should be provided with a link that allows him or her to return to the post view.
If registration is unsuccessful, the form values should remain populated so that the user doesn’t have to type them in again. The user should receive one of the following notifications during the registration process: ❑
A message indicating that the account was created successfully
❑
A message indicating that the account could not be created
❑
A message indicating that an account with the supplied user name and password already exists
Login and Logout The login interface should be accessible from any view in the application. Login is accomplished with the user name and password supplied at the time of registration. If login is successful the following should occur: ❑
The login form must be cleared to prevent anyone from logging in with someone else’s account when the form is redisplayed at logout.
❑
A welcome message containing the user ’s first name should replace the login form.
❑
All the current user ’s account information should become available to the application. The login service will return this data if an account is found.
107
Chapter 11: Project Overview If login is not successful, the user should be notified and told to check the user name and password and try again. Logout involves clearing the data for the current user and returning to the main post view.
Creating New Posts Authors must be able to create new posts in an area restricted only to them. Access can be determined by the accessLevel value of a user ’s account. Authors have an access level of 100. Posts are associated with categories and the author must be able to select a category when creating the posts. The author needs to be able to pick the category, and when the new post is submitted, the associated category id must be submitted along with the post’s text. Authors should not be able to submit a post with a blank title or body. Once an author has clicked the submit button, it should be disabled until a result is returned so that the post can’t be submitted multiple times. If the post is successfully added, the following things should occur: ❑
A message should be displayed to the author that the post has been added.
❑
The submit button should be enabled.
❑
The submission form should be cleared.
If the adding of the post is unsuccessful, display a message informing the author and leave the form populated so that the author does not have to type the post again.
Loading of Existing Posts When users arrive at the application, they should be presented with a list of the most recent posts, and the most recent post from this list should be displayed. The post retrieval service defaults to sorting them by date, so you do not need to worry about sorting them. When the user clicks one of the titles, the full post should replace the currently displayed post.
Commenting System Any time the full details of a post are displayed (be it automatically when the application loads, or by user selection) any comments associated with that post should be displayed as well. If there are no comments associated with the current post, the comment list should be hidden. Only logged-in users may comment, so the commenting form should be displayed only when there is a user logged in.
108
Chapter 11: Project Overview After the user submits the comment, the submit button should be disabled until a result is returned. When a comment is successfully submitted, the comment form should be cleared and the submit button enabled.
Search Capabilities Two types of searches will be available. The first type is a keyword search. This involves sending a list of keywords containing the search terms separated by spaces. The second type is a category search. This involves sending a category id to the search service, which will return all posts in that category. When either of these two types of searches is performed, a list of post titles matching the search results should be presented to the user so that the user can select a post. It should be clear to the user that he or she is viewing posts that match the type of search performed. The results of keyword searches should be headed by the words “Search results for” followed by the search terms, and the results of category searches should be headed by the name of the category.
Summary In this chapter you were introduced to your client, the sample project that you will be creating, and the list of features you’re responsible for. The requirements for each feature will be included in the chapter in which you build it, so you won’t have to keep coming back to this chapter. You will first build the application features listed in this chapter using the suggested (or “typical”) methodology for working with Cairngorm. Once you’ve done that, we will break from the clientcontractor paradigm and examine some alternative ways to build the same features using Cairngorm.
109
Flex Project Setup In this chapter you will set up Flex for the sample project. You will be creating an Adobe Air project. The reasons for this are discussed in the first section of this chapter. In order to compile this project you should have AIR version 1.1 or greater installed. You can download AIR from http://get.adobe.com/air/. While Cairngorm has been included in the sample project, the process of adding Cairngorm to a project will still be covered, as this information will be useful to you for future projects.
Why an AIR Project? A standard Flex project commonly interacts with the server via HTTPServices, RemoteObjects, or some similar class to retrieve and update data. The question for the sample application becomes, how to allow access to such services and data, or simulate them? The overall goal is to provide backend data and services that realistically simulate working (specifically in terms of Cairngorm classes) in a live application, without becoming tied to a particular backend system or language (after all, Flex can be used with a variety of backend languages) or creating overly complex simulations by excluding a backend system. Several ideas were floated, all of which had their strengths and weaknesses. The solution finally agreed upon was to create an Adobe AIR application. If you are unfamiliar with AIR, it stands for Adobe Integrated Runtime. AIR projects are different from standard Flex projects in that they are run as applications on the local machine rather than in a web browser. Because of this, they are afforded capabilities that their web-based counterparts are not. These capabilities, among other things, led to the decision to make the sample application an AIR application. First, AIR applications can store data on the local file system. One way of doing this is in local database files. AIR includes an embedded database system called SQLite. This enables you to have database access on your local machine without requiring you to install any servers.
Chapter 12: Flex Project Setup Second, if you have Flex Builder 3 or higher, you have AIR, as it is installed when you install Flex Builder. This means that you have everything you need to take advantage of AIR’s SQLite capabilities. Thus AIR allows the application to remain backend-agnostic and enables us to avoid issues that arise with other solutions. However, this does not mean that we found the perfect solution. As the database is located on your local machine, you do not actually need to access any remote services. This means that the ServiceLocator is not really usable with this application. However, the use of the ServiceLocator was covered in Chapter 3 and is relatively straightforward. Since services are not really being used, the use of delegates in the sample application will be slightly different from how they are used when they are accessing remote services. However, in the interest of simulating the actual use of Cairngorm as much as possible, they will still be included in the sample application. But rather than accessing services, they will be responsible for interacting with the database via some utility classes discussed in Chapter 13. Because delegates will not access services, the usual methodology of adding the responder to an AsynchToken in the delegate is no longer applicable. Instead you will be calling the responder functions directly in the delegate as part of the simulation. While there are some drawbacks to using delegates this way, the flexibility afforded by the use of Adobe AIR makes it the best choice and allows the sample project to represent general real-world use of Cairngorm.
Creating the Flex Project A Flex Archive project included in the source files for this book contains the basic structure and files that you will need to complete this application. This archive is located in /Resources/code/ch12/FlexBlog. zip. Please use this archive, as it includes the database file that you will need for this application as well as several classes for accessing the database. (These classes will be described in more detail in Chapter 13.) If you are unfamiliar with importing an existing project into Flex Builder, please do the following: Open Flex Builder and, if necessary, switch to the workspace that you will be using for this project. In the Flex Navigator panel, right-click (control + click on MAC) a white (blank) area and select Import from the context menu (see Figure 12-1).
Figure 12-1
112
Chapter 12: Flex Project Setup You should now see a dialog like Figure 12-2. Expand the Flex Builder folder, select Flex Project, and click Next.
Figure 12-2 You should now see a dialog box like Figure 12-3. Click Browse and navigate to where you have saved the source files for this book.
Figure 12-3 Locate the /Resources/code/ch12/FlexBlog.zip file, select it, and click Open (Figure 12-4).
113
Chapter 12: Flex Project Setup
Figure 12-4 When you arrive back at the dialog in Figure 12-2, click Finish. If you expand the folders in the imported project you should see something like Figure 12-5.
Figure 12-5
If you turn your attention to the com.FlexBlog folder, you’ll see the Cairngorm folder structure that you will be using for this project. I want to take a moment to review why I use this particular folder structure. In sample applications that you have seen in this book (e.g., Cairngorm Store vs. Fstop application), the package structure varied greatly. In those applications the package location for classes such as delegates, events, commands, and the ServiceLocator were in different locations. This variance in package structure can make it difficult to track down the location for a given type of class. In the folder structure used in the sample application, it is clearer where everything is located, as each type of class has its own package.
114
❑
commands (stores command classes)
❑
controllers (stores the FrontController class)
❑
delegates (stores service delegate classes)
Chapter 12: Flex Project Setup ❑
events (stores event classes)
❑
models (stores ModelLocator classes)
❑
services (stores the ServiceLocator class)
❑
value objects (stores value object classes)
❑
views (stores MXML views)
You also may have noticed that I have strayed from the standard package-naming convention of using one’s domain name in the package structure (e.g., com.mysitedomain.FlexBlog). The principle of packages is to separate your code into logical units and prevent naming collisions. Something like com. ProjectName does this sufficiently (though I could probably get away with leaving com off as well — I guess that part of the convention still sticks with me). In future projects feel free to structure the top-level folders of the package structure as you see fit. The benefit of the proposed package structure is that each type of class has its own package regardless of the top-level package folders. Regardless of the package structure you use, the important thing is to make sure that everyone working on the project is using the same structure and is familiar with it. If you are the only one working on the project, you can use whatever organization scheme works for you.
Linking Cairngorm to a Project While Cairngorm is included in the sample project, knowing how to associate it with a project will be useful for future projects. I’ll discuss several methods of doing so in this section. If you would rather skip ahead to the next chapter, you can safely do so; you can always come back to this section as a reference when you need to include Cairngorm in one of your projects. Cairngorm comes packaged as a SWC file. If you have never used a SWC file before, a SWC is a compiled archive containing Flex components or other assets. You can find a detailed description of them at http://livedocs.adobe.com/flex/3/html/help.html?content=building_overview_ 5.html or for a screen cast version see http://www.theflexshow.com/blog/index.cfm/ 2009/3/2/The-Flex-Show--Creating-Flex-Components--Episode-1-Project-Setup.
You can get the latest version of Cairngorm at http://opensource.adobe.com/wiki/display /cairngorm/Downloads. There are two versions of Cairngorm: standard and enterprise. The standard version contains the core Cairngorm classes. The enterprise version contains additional classes for working with LiveCycle Data Services, so unless you will be working with these the standard edition should meet your needs. Download the binary zip for the latest version under Latest Milestone Release Builds. Once it has downloaded, unzip it to a location on your hard drive that you can remember. If you have never worked with a SWC file before, associating a SWC file with a project enables you to access in your application the code and components contained in the SWC (in this case the Cairngorm framework classes). There are two ways that you can associate these with a project.
115
Chapter 12: Flex Project Setup The simpler of the two involves the use of the libs folder. This is a new feature added in Flex Builder 3. Any SWC files located in this folder immediately become available to the project. Thus, all you have to do is drag the Cairngorm.swc file from where you downloaded it (the SWC file is located in the bin folder of the archive that you downloaded) into the lib folder of your project (Figure 12-6).
Figure 12-6 The other method is to add a SWC, using the Library path feature of the Flex Build path. To reach this from an existing project, right-click (control + click on MAC) the project name in the Flex Navigator panel and select Properties, as in Figure 12-7.
Figure 12-7 In the Properties dialog box, select Flex Build Path and then click the Library Path button in the righthand panel (Figure 12-8).
116
Chapter 12: Flex Project Setup
Figure 12-8 You can also arrive at a similar screen in the Project Setup Wizard. If you know when you set up the project that you are going to be using Cairngorm, and you like this method of associating SWC files with projects, simply do the following when you create the project. Click the Add SWC button and you will see a dialog like Figure 12-9.
Figure 12-9
Click Browse and navigate to the location to which you extracted the Cairngorm zip file. Select Cairngorm.swc (in the bin folder) and click Choose. Then click OK in the Add SWC dialog. You should now see the Cairngorm.swc file listed in the Properties dialog, as in Figure 12-10.
Figure 12-10
117
Chapter 12: Flex Project Setup Click OK and you are done. All the Cairngorm classes will now be available for you to use in the project.
Summary In this chapter you imported a Flex project archive that will serve as the basis for the sample application. It includes several files for the backend system: these files will be described in more detail in Chapter 13.
118
The Backend In this chapter you will learn about the backend system and some utility classes that have been included in the sample project to make working with it easier.
Description As mentioned in Chapter 12, one of the reasons Adobe Air was chosen for the sample project is its embedded database system called SQLite (see http://www.sqlite.org/). SQLite stores its data in files on the local hard drive. In the sample application, this is the FlexBlog.db file. If you open this file in Flex Builder you will see that parts of it are readable, while others look like gibberish. This is okay, as you will not be editing this file directly. Rather, you will be using the AIR API to modify this file via your code. The AIR classes for working with SQLite are located in the flash.data package, the documentation for which can be found here: http://livedocs.adobe .com/flex/3/langref/flash/data/package-detail.html. As the purpose of the backend system is to give you a more realistic experience working with Cairngorm in the sample application (within the limitations discussed in Chapter 12) and not to have you learn the specifics of working with SQLite (or even Adobe AIR), a couple of classes have been included with the sample application (discussed in more detail in the next section) that will take care of the details of accessing the database.
Database Classes In the source folder of the sample project you will find a folder named sql containing two classes, SQLiteManager and FlexBlogDatabaseManager. You will not be expected to edit any code in these classes, but as they are specific to our backend solution I want to cover how they will be used and the impact they have on delegate classes. The SQLiteManager class simply contains the basic logic needed to work with an SQLite database.
Chapter 13: The Backend The FlexBlogDatabaseManager extends SQLiteManager so that it has database access capabilities and does what you would normally do by calling an HTTPservice or similar class. For example, in a delegate responsible for loading posts you might typically do something like this: package com.FlexBlog.delegates { import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.AsyncToken; import mx.rpc.IResponder; import mx.rpc.http.HTTPService; public class PostDelegate { private var responder:IResponder; public function PostDelegate(responder:IResponder) { this.responder = responder } public function loadPosts():void{ var service:HTTPService =ServiceLocator.getInstance().getHTTPService(‘loadPosts’); var token:AsyncToken = service.send(); token.addResponder(this.responder); } } }
However, in the sample application we will simulate service calls using the SQL classes that have been provided in the sample project as in the following: package com.FlexBlog.delegates { import mx.rpc.IResponder; public class PostDelegate { private var responder:IResponder; public function PostDelegate(responder:IResponder) { this.responder = responder } public function loadPosts():void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.loadPosts(); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true, {posts: posts}) this.responder.result(event) }catch (error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
120
Chapter 13: The Backend If you look at the loadPost function in this second example, you can see that FlexBlogDatabaseManager is implemented as a singleton and is retrieved via the static getInstance method. If you look at the line var posts:Array =
dbManager.loadPosts();
you can see that the FlexBlogDatabaseManager class has a method called loadPosts. This is how services are being simulated. For each service such as loadPosts there will be a method on the FlexBlogDatabaseManager that can be called. The specifics of what methods on the FlexBlogDatabaseManager you need to call, what data they return, and what data they expect to be passed, will be addressed as you build specific features of the application.
Impact on Cairngorm Usage As mentioned in Chapter 12, the choice of using Adobe Air and placing the database on the local machine is not without its drawbacks. Specifically, since no HTTPServices are being called, the normal method of allowing the delegate responder to react to the HTTPServices call will not work. For simplicity’s sake we are using the SQL API in synchronous mode (otherwise you end up with a bunch of callback functions). Because we are using this mode, you will have to use try-catch blocks to catch any errors that occur during the SQL execution. In doing so, you can see that the responder functions are being directly called in the try-catch blocks for their respective situations (everything went okay versus something went wrong). So while the ServiceLocator has been taken out of the mix and there are some differences in how delegates are being constructed to access the local database, you are still for the most part following the standard Cairngorm flow for accessing data. On the positive side, there is an added benefit of using value objects with this system. You learned in Chapter 9 that it is conventional to have the properties on value objects match their representations on the server side or data store. This is so they can be used as data transfer objects. As it turns out, one of the SQL classes has a feature that will make value objects a nice means of representing data in the application. Specifically, the SQLStatement has a property called itemClass. The documentation for this property (http://livedocs.adobe.com/flex/3/langref/flash/data/ SQLStatement.html) describes it as follows: By specifying a class for the itemClass property, each row returned by a SELECT statement executed by this SQLStatement instance is created as an instance of the designated class. Each property of the itemClass instance is assigned the value from the column with the same name as the property.
Since value objects are typically set up with public properties that match their data stores, this makes them perfect candidates for the itemClass property.
121
Chapter 13: The Backend The net effect of using value objects for the itemClass is that you will receive arrays containing objects of the value object type that you can use to update the model. For example, the following method on the FlexBlogDatabaseManager class would return an array containing PostVO objects: public function getPosts ():Array{ this.statement.itemClass = PostVO; return this.query(“SELECT * FROM posts;”).data; }
Since this is done in the FlexBlogDatabaseManager utility class, it will be taken care of for you: you will simply be told what value object to expect back from the service in the chapters where you are using them.
Summary In this chapter you were introduced to how you will be working with the backend system and some of the differences that will occur in delegate classes when you work with the backend system.
122
Main Application Setup In this chapter you will set up the main application file, as you need a place to test out your views and other Cairngorm classes. You will start by creating the ModelLocator and FrontController classes. Then you will set up the namespaces to access your views and FrontController. You must have an instance of the FrontController in the main application in order for other Cairngorm classes to have access to it (the same is true for the ServiceLocator if you are using that), so after creating the appropriate namespaces, you will add these instances to the main application. Finally, you will create a main application view that will house the other components that you create.
Creating the FrontController In order for Cairngorm events to be able to dispatch to the FrontController, there must be an instance of the FrontController in the main application. You will add this instance later in this chapter, when you set up the main application file. To get started on this process, create a class named FlexBlogController that extends com.adobe. cairngorm.control.FrontController in the com.FlexBlog.controllers package. (See Chapter 5 for specific instructions, with screenshots, for creating a FrontController class.) Modify the class to look as follows: package com.FlexBlog.controllers { import com.adobe.cairngorm.control.FrontController; import com.FlexBlog.events.*; import com.FlexBlog.commands.*
Chapter 14: Main Application Setup public class FlexBlogController extends FrontController { public function FlexBlogController() { super(); } } }
Remember to add imports for your events and commands packages, since all your Cairngorm events and command classes will eventually have to be registered here. Since you do not currently have any classes in the events and commands packages, auto-complete will suggest only the controllers package (because that has the FrontController class in it), and so you will have to type out the names of the rest of the packages manually.
Creating the ModelLocator The ModelLocator class, unlike the FrontController and the ServiceLocator, is not required to be in the main application file in order for other classes to work, but you will need it for many of the classes that you will create later. Create a class named FlexBlogModel in the com.FlexBlog.models package. (See Chapter 4 for specific instructions with screenshots.) Edit the class to look as follows: package com.FlexBlog.models { [Bindable] public class FlexBlogModel { private static var instance:FlexBlogModel = null; public function FlexBlogModel(se:SingletonEnforcer) { } public static function getInstance():FlexBlogModel{ if (FlexBlogModel.instance == null){ FlexBlogModel.instance = new FlexBlogModel(new SingletonEnforcer()); } return FlexBlogModel.instance; } } } class SingletonEnforcer{};
124
Chapter 14: Main Application Setup Make sure that you have added the Bindable metatag before the class definition. This allows other classes to bind to properties on the model. Of all the singletons used in the Cairngorm framework, the ModelLocator is the only one that you need to implement as a singleton on your own. For the singleton implementation, make sure that you have: ❑
Created a private static variable named instance that will hold the single instance of the class
❑
Created an internal class named SingletonEnforcer outside of the package definition
❑
Created the constructor method to take in a parameter of type SingletonEnforcer
❑
Created a public static method named getInstance that creates the single instance, if it has not already been created, and returns it
Recall from Chapter 4 that the SingletonEnforcer is a private class. When a class is defined in an ActionScript file outside the package definition, only the main class defined in that file may access the class. This ensures that the constructor cannot be called outside the class, since ActionScript does not support private constructors. Technically you can still call the constructor, but if you do the compiler will complain that you are not passing the required SingletonEnforcer parameter, as it is not possible to create one outside the ModelLocator class.
Main Application Setup Now that you have your core classes created, it is time to add them to the FlexBlog.mxml main application file. Before you can do this, you will need to add some namespaces to the main application tag. Edit the main application tag as follows:
These namespaces will enable you to access classes and MXML components from the views, controllers, and services packages. Next, add the instances of the FrontController and ServiceLocator by adding the following to the main application:
If you are new to using namespaces, note that instead of the mx: prefix used for standard Flex tags, the prefix for the FrontController matches the namespace you defined for the package that contains the component or class. The same will be true for the views package, as you will see shortly. You are free to use whatever namespaces you like in your own projects: they simply need to be unique in name and value. Now that you have added the FrontController class to the main application, the FrontController class will be able to intercept and respond to any Cairngorm events that are dispatched.
125
Chapter 14: Main Application Setup Next you are going to create a main view component that will house your other views. In the com.FlexBlog.views package create a new component named MainView. Base the component on a VBox and clear the height and width properties. Once you have created it, edit the component to match the following: .appTitle{ font-weight:bold; font-size:24px; } .viewHeader{ font-weight:bold; font-size:18px; }
At the top of the view, you see some styles that will be used for labels in other parts of the view. The first component tag that you see is an ApplicationControlBar. This contains your application title and several link buttons that will be used to switch among the main views: Posts, Register, and Write. Below the ApplicationControlBar is a ViewStack containing several HBox tags, one for each of your main views. Since each of these HBox tags is a direct child of the ViewStack tag, you will be able to switch main views by changing the selectedIndex property of the ViewStack. As you build the features of the application you will add the views that you create to the HBox containers of the ViewStack. Now you can add your main view component to the main application. Add the following to the main application file immediately before the closing application tag:
126
Chapter 14: Main Application Setup If you debug the application you should see something like Figure 14-1.
Figure 14-1
Note that since this is an AIR application, it actually opens in an application window instead of a web browser. If the application does not compile, compare your code to the code found in the source files for the book in /Resources/code/ch14/.
Summary In this chapter you created the ModelLocator and FrontController and added them to the main application file so that future components will be able to access them. You also created a MainView component that provides spaces for views that you will create later. Separating your views into individual files makes each file more concise and means that it needs to contain only the code responsible for making that view work. This makes tracking down code related to a particular view easier, as you do not have to search through one large view file containing code for multiple event handlers and so on. Now that you have the main application file set up, you are ready to begin building the individual application features.
127
User Registration In this chapter you will create the user registration feature. Before doing so, I want to take some time to point out a few things about the chapters where you create features for the sample project, as this is the first such chapter. For each chapter where you create a feature, you will start by reviewing the requirements for that feature and make some decisions about how you will implement them. After those decisions have been made, you will follow a prescribed order for creating your classes. This order is not mandatory, nor endorsed by any specific party associated with Cairngorm, but I have chosen it because I tend to start by creating classes that will be used by other classes. First you will identify, and if necessary create, any value object classes (in some cases you will use value objects that have already been created). You create these first as they will sometimes be used by your event classes to pass around data. They will also be used by views to display data and as variables in the ModelLocator class. Next you will create the event classes, as these are necessary to trigger the functionality of the other classes and are referenced by your command classes. By knowing the implementation of your event classes, you can structure your command classes to take advantage of the properties of a specific event class. Additionally, creating event classes at this point allows them to be available when you create your views, as views will need to dispatch these events. Next you will create your command classes. Since it is the command classes that update the model, any properties that need to be added to the model will generally be added at this point. Additionally, after the commands have been created, you will have an event-command pairing that you can then add to the FrontController. After all these classes have been created you will create your views by setting up your bindings to the models and dispatching the appropriate event classes. Finally, after you have all of the individual components created, you will add them to the main application and test them.
Chapter 15: User Registration Again, there is some wiggle room in this order. The basic idea is first to create components that will be used by other components. If in the future you find another way that works better for you, you should feel free to use it in your own projects. The FlexBlogDatabaseManager class has been updated for this chapter. Please replace the current FlexBlogDatabaseManager.as file in the /src/sql/ folder with the one located in the source files for this chapter found in /Resources/code/ch15/src/sql/.
Registering Users Over view The registration feature is required to have the following properties: ❑
It is to collect the user ’s first name, last name, user name, and password.
❑
All fields are required, and user names and passwords must be at least five characters each.
❑
The user should be able to cancel the registration process at any point before submitting it. When registration is canceled, the user should be taken back to the main posts page.
❑
The submit and cancel buttons should be disabled while the registration is in progress to prevent the user taking an action while registration is in progress.
❑
If an account with the supplied user name and password already exists, the user should be notified of it.
❑
If registration is successful, the registration form should be cleared and the user should be notified.
❑
If registration fails, the form should remain populated and the user should be notified that something went wrong.
❑
Collecting the user data is simply a matter of creating a form that collects the information and submits that data to the registration service.
❑
To meet the requirements of making the form fields mandatory and enforcing the length requirement for the user name and password, you will use the built-in Flex Validation classes: (http://livedocs.adobe.com/flex/3/html/help.html?content=validators_2 .html).
You can enable cancellation simply by providing a cancel button that returns the user to the main posts view. Notifying the user about the status (pass, fail, account exists) of the registration process is simply a matter of detecting the status of the registration process and displaying an appropriate message. Therefore, you need to decide where and how to display this message to the user. Since you know from the specification that multiple features will be displaying feedback to the user, you decide to use a common area in the application to display these messages. This leaves the clearing of the form, which you will accomplish by triggering a function in the registration view that clears the form when the registration process is successful.
130
Chapter 15: User Registration In addition to the features outlined above, you also need some way to get to your registration view. The shell application already contains a button to reach this view; you simply need to add the code to do so.
Value Objects The registration process is centered on the idea of a user. You are collecting data related to a particular user for the purpose of creating an account. This collection of data will be represented by a value object class called UserVO. The users table has the following columns: ❑
userId
❑
firstName
❑
lastName
❑
userName
❑
password
❑
accessLevel
These columns represent the data that needs to be represented by the UserVO. Recall that aside from representing related data within your application, value objects are also used to transfer data to and from the server. Hence, the properties of the value object should match those of the properties used to represent the object on the server side (in this case database column names). In the com.FlexBlog.valueobjects package create the UserVO class as follows: package com.FlexBlog.valueobjects { public class UserVO { public var userId:int; public var firstName:String; public var lastName:String; public var userName:String; public var password:String; public var accessLevel:int; } }
You also know that you intend to display messages to the user and that these messages can be either indications of success or notifications that something went wrong. You are going to want the user to be able to distinguish between a success message and an error message. A simple way of doing this is to display success messages in one color and error messages in another. A notification can be said to consist of both the actual message and the type of message (which you can use to determine how to display it). You will create a value object that will contain the message and its associated type so that the view displaying the notification can vary its visual representation based on the message type.
131
Chapter 15: User Registration In the com.FlexBlog.valueobjects package create a class named UserNotificationVO and edit to match the following: package com.FlexBlog.valueobjects { public class UserNotificationVO { public static const ERROR_MESSAGE:String = ‘errorMessage’; public static const SUCCESS_MESSAGE:String = ‘successMessage’; public var message:String = ‘‘; public var type:String = ‘‘; } }
Now that you have a class that represents a user notification, you can have a property on the model that your notification view binds to in order to display messages to the user. This view can use the type attribute to determine how it will display the message.
Event Classes To get to the registration view you need some method of changing the main view. You could simply have the buttons in the main view change the selected index of the main ViewStack — that would solve the immediate problem, but what if a component nested deeper in the application wants to change the main view? For example, the cancel button on the registration form is supposed to return the user to the posts view. Such a nested component would have to either have a reference to the ViewStack or dispatch some event that the main ViewStack would have to listen for. The latter is a possible solution, but since you are using Cairngorm anyway, let’s examine how Cairngorm can solve this issue. You can bind the selectedIndex of the ViewStack to a property on the ModelLocator and then have your sub-views dispatch an event that is responsible for changing the main view by updating the variable on the ModelLocator. In the com.FlexBlog.events package create a new event named ChangeMainViewEvent and edit it to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class ChangeMainViewEvent extends CairngormEvent { public static const CHANGE_MAIN_VIEW:String = ’changeMainView’; public static const POSTS_VIEW:int = 0; public static const WRITE_VIEW:int = 1; public static const REGISTER_VIEW:int = 2; public var goTo:int; public function ChangeMainViewEvent(goTo:int, bubbles:Boolean=false,
132
Chapter 15: User Registration cancelable:Boolean=false) { super(CHANGE_MAIN_VIEW, bubbles, cancelable); this.goTo = goTo; } } }
Note that there is a constant for each view. This allows your sub-views to do something like the following: New ChangeMainViewEvent(ChangeViewEvent.POST_VIEW).dispatch();
Let’s say that you then decide to use states instead of a ViewStack. All you have to do is update the ChangeMainViewEvent to use strings, change the ModelLocator property to a string, and have your main view (which, if you’ve decided not to use a ViewStack, you’ll be changing anyway) bind to the appropriate property to change states. While that may sound like a decent number of changes, what you don’t have to change is how your subviews dispatch the event. Imagine that they were all directly passing a numeric index. You would have to go back, update all your views, and change the way they dispatch the event. This is potentially a much larger number of changes. Now turn your attention to the event responsible for registration. This class is responsible for passing along the data the command class needs to process the registration event. To pass this data, you will create a property on the event that holds an instance of a UserVO. In the com.FlexBlog.events package create a new Cairngorm event named RegisterUserEvent and edit it to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.UserVO; import com.adobe.cairngorm.control.CairngormEvent; public class RegisterUserEvent extends CairngormEvent { public static const REGISTER_USER:String = ‘registerUser’; public var user:UserVO; public function RegisterUserEvent(user:UserVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(REGISTER_USER, bubbles, cancelable); this.user = user; } } }
You have imported the com.FlexBlog.valueobjects.UserVO and created a public instance variable user that will hold an instance of a UserVO that is passed in via the constructor. This will allow the command class to have access to this UserVO instance.
133
Chapter 15: User Registration
Delegate Classes Now that you have the event and command classes created, you are going to create a delegate to access the service. In the com.FlexBlog.delegates package create a class named RegisterUserDelegate and edit it to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.UserVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class RegisterUserDelegate { private var responder:IResponder; public function RegisterUserDelegate(responder:IResponder) { this.responder = responder; } public function register(user:UserVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var userAdded:int = dbManager.registerUser(user); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:userAdded}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
Here we are seeing the first occurrence of interaction with the backend using the SQL utility classes. The register method takes in a UserVO as a parameter and passes it to the registerUser method of the FlexBlogDatabaseManager. The registerUser method returns an integer indicating the success or failure of the add user operation. If an account with the supplied user name and password exists, you will get back a value of -1; otherwise you will get back the number of rows created by the query, which should be 1. If a numeric value is returned, the delegate passes this value via the result object of a ResultEvent, which is passed to the result function of the responder. If something goes wrong during the execution of the query, the catch block is triggered and the fault function of the responder is called, allowing your responder to act accordingly.
134
Chapter 15: User Registration This more or less mimics how things would occur if you were using HTTPServices (or some other remote data access class) and is the basic methodology that you will be using in your delegate classes in the sample application.
Command Classes Since you will be changing main views by binding to a variable on the ModelLocator, you need to add that variable to the model. Add the following to the FlexBlogModel: public var mainView:int;
Now you need a command class to update this variable. In the com.FlexBlog.commands package create a new command class named ChangeMainViewCommand and edit to match the following: package com.FlexBlog.commands { import com.FlexBlog.events.ChangeMainViewEvent; import com.FlexBlog.models.FlexBlogModel; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; public class ChangeMainViewCommand implements ICommand { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { var evt:ChangeMainViewEvent = event as ChangeMainViewEvent; this.model.mainView = evt.goTo; } } }
This command updates the mainView property of the ModelLocator. To return for a moment to the idea of dealing with change (e.g., changing the main view to be something other than a ViewStack), you do not even have to touch the command class, as all it does is update the model with the value passed in the goTo property of the event. As long as you have updated the event and the model to expect the same type of parameter, you are good to go. Now register the ChangeMainViewEvent and ChangeMainViewCommand in the FlexBlogController: public function FlexBlogController() { super(); addCommand(ChangeMainViewEvent.CHANGE_MAIN_VIEW, ChangeMainViewCommand); }
135
Chapter 15: User Registration The registration command class needs to take the UserVO passed in by the event class and send it to the register method of the RegisterUserDelegate. The command class then needs to be able to respond to the results from the service. If registration is successful, the command needs to update the model to let anyone who may be listening know that the registration process successfully (or otherwise) completed. Add the following property to the FlexBlogModel: public var registrationStatus:String;
For either success or failure, the message returned from the service needs to be displayed to the user. You will use the UserNotificationVO class you created to do this. Import the UserNotificationVO into the FlexBlogModel: import com.FlexBlog.valueobjects.UserNotificationVO;
Then add a userNotification property to the model: public var userNotification:UserNotificationVO;
Now create a new command class named RegisterUserCommand in the com.FlexBlog.commands package, and edit it to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.RegisterUserDelegate; import com.FlexBlog.events.RegisterUserEvent; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class RegisterUserCommand implements ICommand, IResponder { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { this.model.registrationStatus = ‘‘; var evt:RegisterUserEvent = event as RegisterUserEvent; var delegate:RegisterUserDelegate = new RegisterUserDelegate(this); delegate.register(evt.user); } public function result(data:Object):void { var event:ResultEvent = data as ResultEvent; var notification:UserNotificationVO = new UserNotificationVO(); if (event.result.added ==1){
136
Chapter 15: User Registration this.model.registrationStatus = ‘success’; notification.message = ‘Your account has been created.’ }else if(event.result.added ==-1){ this.model.registrationStatus = ‘failed’; notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘An account with that user name and password already exists.’ }else{ notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘Account could not be created at this time. Please try again later.’ } this.model.userNotification = notification; } public function fault(info:Object):void { this.model.registrationStatus = ‘fault’; var notification:UserNotificationVO = new UserNotificationVO(); notification.message = ‘There was an error processing your registration, please try again.’ notification.type = UserNotificationVO.ERROR_MESSAGE; this.model.userNotification = notification; } } }
The execute method clears the registration success flag on the model, setting it to an empty string to indicate that registration is in process. It then takes the UserVO and passes it to the RegisterUserDelegate register method. The result method checks to see whether the result returned in the added property of the result object of the ResultEvent that you created in the delegate indicates that there was an existing account, or that the account has been added. It then sets the success flags on the model accordingly and displays a message to the user by updating the model with a new UserNotificationVO. The fault function sets the success flag on the model to the string fault and displays a message to the user again using the UserNotificationVO. Now that you have a corresponding RegisterUserEvent and RegisterUserCommand, add them to the FlexBlogController: addCommand(RegisterUserEvent.REGISTER_USER, RegisterUserCommand);
Views First you will deal with the view responsible for displaying messages to the user.
137
Chapter 15: User Registration In the com.FlexBlog.views package create a new component based on HBox. Name it NotificationDisplay and clear the height and width values. Edit the component to match the following: .errorStyle{ color:#95090f; }
Starting at the top of the component, notice that the visible property has been tied to the length of the message property. Therefore, this component will display itself only if there is a message to display. There are two private variables: message and messageStyle. These are used to tell other parts of this component what message to display and what style to use. Next you have a setter function named notification that takes in a UserNotificationVO as a parameter (make sure you have imported the com.FlexBlog.valueobjects.UserNotificationVO class). This function sets the component message property to the message contained in the UserNotificationVO and uses the type property of the UserNotificationVO to determine how to display the message by setting the messageStyle property to a corresponding CSS style. This function will be bound to the userNotification property on the ModelLocator so that the UserNotificationVO referenced there will be passed as a parameter to this function any time the UserNotificationVO changes. The clearMessage function simply sets the message and messageStyle properties to empty strings. Since the visibility of the component is tied to the length of the message string, this essentially hides the component.
138
Chapter 15: User Registration (You could do the same thing by modifying the userNotification property on the ModelLocator, but to do this you would have to create another Cairngorm event and command class, as views should not update the model directly. Since in this case all we are interested in doing is hiding the message, the clearMessage function works fine.) Now move on to creating the registration form. In the com.FlexBlog.views package create a new component based on Form. Name it RegistrationForm and clear the height and width boxes. Edit the component to match the following:
Most of this component deals with form validation and uses the Flex validation classes. Since these are standard Flex classes, I am not going to cover the specifics of using them, but rather focus on the Cairngormspecific code. A link to the documentation for the validation classes was provided in the “Registering Users Overview” section of this chapter, should you be interested in learning more about them.
140
Chapter 15: User Registration The first piece of Cairngorm-specific code appears in the doRegistration function, after all the validation checks have been performed in the else condition of the following piece of code: if (errorMesssage){ Alert.show(‘Please correct the highlighted fields. Mouse over each fieldfor an error description.’); }else{ this.registerButton.enabled = false; this.cancelButton.enabled = false; var user:UserVO = new UserVO(); user.firstName = this.firstName.text; user.lastName = this.lastName.text; user.userName = this.userName.text; user.password = this.password.text; new RegisterUserEvent(user).dispatch(); }
This code disables the register and cancel buttons, fulfilling the requirement that they be disabled when the form is submitted. Next a UserVO object is created and populated with the values from the form. This UserVO is then passed to a new RegisterUserEvent and that event is dispatched. The next piece of Cairngorm code occurs in the click attribute of the cancel button:
Here a ChangeMainViewEvent is dispatched to return the user to the posts view. Finally, take note of the registrationComplete setter function. This will be bound to the registrationStatus property of the model: public function set registrationComplete(status:String):void{ if(status == ‘success’){ this.clearForm(); } if(status!=’’){ this.registerButton.enabled = true; this.cancelButton.enabled = true; } }
Thus, any time the registrationStatus property changes, this function will be called and will be passed the registrationStatus value as a parameter. This function handles the clearing of the form and the enabling of the register and cancel buttons. Now that you have the views created, you are going to add them to the MainView component and make a few other changes.
141
Chapter 15: User Registration First make sure that you have a namespace pointing to your com.FlexBlog.views package in the root tag of the MainView component:
You are going to need a reference to the ModelLocator, since you will be binding view properties to it. Add the following to the MainView component:
Next modify the Register button so you can get to the registration view by dispatching a ChangeMainViewEvent:
Do the same for the Posts button:
Make sure Flex imported the event class for you when you added it to the buttons (it should have). Now add your NotificationDisplay component between the ApplicationControlBar and the ViewStack:
Note that the notification property (actually a setter function) has been bound to the userNotification property of the model. Now bind the ViewStack selectedIndex property to the mainView property of the ModelLocator:
Finally, add your RegistrationForm component to the registerView HBox:
142
Chapter 15: User Registration Note that the registrationComplete property (again a setter function) has been bound to the registrationStatus property of the ModelLocator. Now that you have all the components tied together and added to the main application, it is time to test it out.
Implementation and Testing If you debug the application, you should see something like Figure 15-1.
Figure 15-1 Click the Register button in the ApplicationControlBar and you should see something like Figure 15-2.
Figure 15-2 You are not going to bother testing valid values for each input, but you should make sure that form validation is at least being triggered. Try submitting the form with no values and you should see an alert like the one in Figure 15-3.
Figure 15-3
Click OK to close the alert and you should see that the form fields have been highlighted in red. If you mouse over each one you should see a message indicating how to fix the problem, as in Figure 15-4.
143
Chapter 15: User Registration
Figure 15-4 Since you have a basic idea that validation is working, you will leave this page as is for now. Now create the following account and click Register: ❑
First Name = test
❑
Last Name = user
❑
User Name = testuser
❑
Password = password
You should now see something like Figure 15-5, which shows the success message with the form cleared.
Figure 15-5
The disabling and enabling of the buttons occurs so quickly that it may be hard to tell if that part of the application is working, but you should see at least a quick flash of them becoming grayed out. Now try creating the exact same account again and you should see something like Figure 15-6.
Figure 15-6
144
Chapter 15: User Registration From these two cases you can see that the message style is being varied, with error messages being displayed in red rather than gray. Now test that the Clear button hides the message. Next, confirm that the Cancel button takes you back to the posts view (Figure 15-1 again). At this point we have confirmed that the basics of the registration process work and that the messaging system works. We will not worry about the fault condition, as all that does is display a message. If you did not get the expected results, compare your code to the sample code found in /Resources/code/ch15/.
Summary In this chapter you implemented the user registration feature. Let’s take a moment to examine how Cairngorm was used in implementing this feature. You created ChangeMainViewEvent and ChangeMainViewCommand classes, which enable you to navigate to the main views of the application. These classes use constants to correspond to the main views so that they are more flexible in case you decide to change how the main view operates. You created RegisterUserEvent, RegisterUserCommand, and RegisterUserDelegate classes, all of which make use of a UserVO class to pass data so that an account can be created. You created a UserNotificationVO class that enables you to display different types of messages to the user by indicating a message type.
145
User Login In this chapter you will create the user login and logout feature. You will start by reviewing the requirements and then move on to creating the Cairngorm classes and views. The FlexBlogDatabaseManager class has been updated for this chapter. Please replace the current FlexBlogDatabaseManager.as file in the /src/sql/ folder with the one located in the source files for this chapter found in /Resources/code/ch16/src/sql/.
Login and Logout Over view The login and logout feature has the following requirements: ❑
The login interface should be accessible from any view in the application.
❑
Login is accomplished using the user name and password supplied at the time of registration.
❑
If login is successful the login form must be cleared.
❑
A welcome message containing the user ’s first name should be displayed to replace the login form.
❑
All the current user ’s account information should become available to the application. The login service will return this data if an account is found.
❑
If login is not successful, the user should be notified and told to check the user name and password and try again.
❑
Logout consists of clearing the data for the current user and returning to the main posts view.
Chapter 16: User Login To make the login form accessible from anywhere in the application, you will add it to the ApplicationControlBar. The login form will be a simple form that collects the user name and password and has a submit button. To clear the form you will use the same methodology that you used for the registration form: by binding a setter function to a property on the ModelLocator indicating the success of the login process. The same variable on the ModelLocator will determine whether a welcome message should be displayed and whether to redisplay the form when the user logs out. The displaying of messages related to the login process will be handled in the login command class. This will be done by updating the userNotification property of the ModelLocator by creating a new UserNotificationVO object. The login service will return a UserVO that can be used to represent the current user, making that user ’s account information available. Logout is simply a matter of setting the UserVO property on the ModelLocator to null.
Value Objects Since the process of login centers around the idea of a user, you will use the existing UserVO to represent the current user. Upon successful login, the user object returned from the login services will be used to populate a currentUser property on the ModelLocator. Add the following property to the FlexBlogModel: public var currentUser:UserVO;
Make sure that Flex has added the following import statement for you: import com.FlexBlog.valueobjects.UserVO;
Since the login process once again requires you to display notifications to the user, you will also be making use of the UserNotificationVO in the command classes to set the userNotification property of the ModelLocator.
Event Classes You will need an event class for both the login and logout functionality. Start with the login event by creating a new event class named LoginEvent in the com.FlexBlog.events package. Edit it to match the following:
148
Chapter 16: User Login package com.FlexBlog.events { import com.FlexBlog.valueobjects.UserVO; import com.adobe.cairngorm.control.CairngormEvent; public class LoginEvent extends CairngormEvent { public static const LOGIN:String = ”loginEvent”; public var user:UserVO; public function LoginEvent(user:UserVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(LOGIN, bubbles, cancelable); this.user =user; } } }
The LoginEvent takes in a UserVO that it will pass along to the LoginCommand. This UserVO will simply have the userName and password properties populated so that they can be used to locate the account in the delegate class. Next, create the logout event by creating a new event class named LogoutEvent in the com.FlexBlog .events package. Edit it to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class LogoutEvent extends CairngormEvent { public static const LOGOUT:String = ‘logoutEvent’; public function LogoutEvent(bubbles:Boolean=false, cancelable:Boolean=false) { super(LOGOUT, bubbles, cancelable); } } }
This event does not need to transfer any data and is only being used to trigger the corresponding command class that will handle the login procedure.
Delegate Classes You need a delegate class for login functionality. However, since logout requires only that the data for the current user be destroyed, you do not really need to retrieve or update any data from the database and no delegate class is required.
149
Chapter 16: User Login Create a new delegate class named LoginDelegate in the com.FlexBlog.delegates package. Edit it to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.UserVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class LoginDelegate { private var responder:IResponder; public function LoginDelegate (responder:IResponder) { this.responder = responder; } public function login(user:UserVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var isUser:UserVO = dbManager.login(user); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{user:isUser}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The LoginDelegate login function takes in a UserVO and passes it to the login function on the FlexBlogDatabaseManager class to simulate the service call. If an account is found, the login method of the FlexBlogDatabaseManager class returns a UserVO object with the account information for that user. If no account is found, the UserVO returned will be null. The UserVO is then passed back to the command class by being assigned to the result property on the ResultEvent. If the FlexBlogDatabaseManager login method fails for some reason, the fault function of the responder is called.
Command Classes The login and logout procedures each need a command class. Start with the login command by creating a new command class named LoginCommand in the com .FlexBlog.commands package. Edit it to match the following:
150
Chapter 16: User Login package com.FlexBlog.commands { import com.FlexBlog.delegates.LoginDelegate; import com.FlexBlog.events.LoginEvent; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class LoginCommand implements ICommand, IResponder { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { var evt:LoginEvent = event as LoginEvent; var delegate:LoginDelegate = new LoginDelegate(this); delegate.login(evt.user); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; var notification:UserNotificationVO = new UserNotificationVO(); if(evt.result.user){ this.model.currentUser = evt.result.user; }else{ notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘No account with that user name and password could be found’; this.model.userNotification =notification; } } public function fault(info:Object):void { var notification:UserNotificationVO = new UserNotificationVO(); notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘A server error occured while trying tolog you in. Please try again later”;’; this.model.userNotification =notification; } } }
The execute function takes the UserVO object passed in via the LoginEvent and passes it to the LoginDelegate. The result function detects if the UserVO passed back by the delegate is null (meaning no account was found). If the object is not null, the currentUser property of the ModelLocator is set to the UserVO, making the account data available to the application. If the UserVO is null, then a message is displayed to the user, by means of the UserNotificationVO class, that the account could not be found. The fault function displays a message to the user, by means of the UserNotificationVO, that something went wrong with the login process that is not related to an invalid user name and password.
151
Chapter 16: User Login Now create the logout command by creating a new command class named LogoutCommand in the com.FlexBlog.delegates package. Edit it to match the following: package com.FlexBlog.commands { import com.FlexBlog.events.ChangeMainViewEvent; import com.FlexBlog.models.FlexBlogModel; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; public class LogoutCommand implements ICommand { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { this.model.currentUser = null; new ChangeMainViewEvent(ChangeMainViewEvent.POSTS_VIEW).dispatch(); } } }
This command simply sets the currentUser property on the ModelLocator to null and redirects the user to the post view so that if a user logs out in a restricted area, that area of the application will not remain accessible. Note that since this command is not using a delegate, it is not necessary to implement the IResponder interface. Now that you have the corresponding event and command classes for the login and logout processes, add them to the FrontController: addCommand(LoginEvent.LOGIN,LoginCommand); addCommand(LogoutEvent.LOGOUT,LogoutCommand);
Views Now you need a view that will allow the user to enter a user name and password. Additionally, this view will display the welcome message and provide a logout mechanism when the user is logged in. In the com.FlexBlog.views package, create a new component based on HBox. Name it Login. Remember to clear the height and width from the creation dialog. Edit the Login component to match the following: /mx:HBox>
Starting at the top of the file, the following classes are imported: import import import import
com.FlexBlog.events.LogoutEvent; com.FlexBlog.events.LoginEvent; com.FlexBlog.valueobjects.UserVO; com.FlexBlog.models.FlexBlogModel;
The component then creates a reference to the ModelLocator: [Bindable] private var model:FlexBlogModel =FlexBlogModel.getInstance();
The doLogin function constructs a UserVO from the form inputs, which is then passed to a LoginEvent, which is then dispatched: private function doLogin(event:MouseEvent):void{ var user:UserVO = new UserVO(); user.userName = this.userName.text; user.password = this.password.text; new LoginEvent(user).dispatch(); }
153
Chapter 16: User Login The loggedin setter function will be bound to the currentUser property of the ModelLocator. This function checks to see if the currentUser property is null (logged out). If the property is not null, then a user has logged in and the form should be cleared: public function set loggedin(obj:Object):void{ if (obj){ this.userName.text =’’; this.password.text = ’‘; } }
Next you see an HBox that contains the login form:
The visible property of this HBox has been bound to an expression check to see if the currentUser property of the ModelLocator is null (meaning there is no logged-in user). This has the effect of hiding or showing the login based on whether a user is logged in. The loginButton triggers the doLogin function discussed earlier. Finally, there is an additional HBox that displays the welcome message and Logout button:
Here the visibility property is tied to an expression checking to see if the currentUser property of the ModelLocator is not null (meaning there is a logged-in user); again, this has the effect of showing or hiding this view based on the login status of a user. The logoutButton dispatches a LogoutEvent, triggering the logout logic.
Implementation and Testing Now that you have your components and classes created, it is time to test the system out. In the MainView components add the following to the ApplicationControlBar after the link buttons:
Note that the loggedin setter function is being bound to the currentUser property on the ModelLocator.
154
Chapter 16: User Login If you debug the application you should see something like Figure 16-1 (you may need to maximize the application at this point to see everything).
Figure 16-1 Try logging in with the following account: ❑
User name: poe
❑
Password: password
You should now see something like Figure 16-2.
Figure 16-2
Now switch to the Register view so that you can confirm that you are returned to the posts view when you log out. Click the Logout button and your screen should once again look like Figure 16-1. Now try logging in with a nonexistent account (you can even leave the form blank) and you should see something like Figure 16-3, with a line saying no account has been found.
Figure 16-3
155
Chapter 16: User Login These tests confirm that: ❑
You can log into an account that was created via the registration process.
❑
The user ’s account information is available, as shown by the user ’s first name being displayed in the welcome message.
❑
The form is cleared after a successful login.
❑
The logout functionality works and returns you to the posts view.
❑
The user data for the current user is destroyed at the time of logout, as the visibility of the login views is tied to the currentUser property’s being null.
If you did not get the expected results, compare your code to the sample code found in /Resources/code/ch16/.
Summary In this chapter you created the login system. Let’s take a moment to review how you used Cairngorm to do this. You were able to reuse the UserVO and UserNotificationVO value objects to pass data between classes and display notifications to the user. You created LoginEvent and LogoutEvent classes that triggered the appropriate logic. The LoginEvent took in a UserVO object that was then passed to the command class. The Logout event simply triggered the corresponding command class. You created a LoginDelegate with a login function that took in a UserVO and passed it to the FlexBlogDatabaseManager login function to simulate a service call. The login function of the FlexBlogDatabaseManager class returned a UserVO that was then passed back to the command class via the result responder method. Since logout did not require accessing the database, no logout delegate class was required. You created LoginCommand and LogoutCommand classes. The LoginCommand class took the UserVO passed in via the LoginEvent class and passed it to the login function of the LoginDelegate. The result function determined whether the UserVO passed back by the LoginDelegate was null (in other words, whether an account had been found). If an account was found, the currentUser property of the ModelLocator was updated with the UserVO object. If no account was found, the user was notified by means of a UserNotificationVO. The fault also makes use of the UserNotificationVO to notify the user that something has gone wrong that is not related to an account not being found. The LogoutCommand class simply set the currentUser property of the ModelLocator to null and returned the user to the posts view by dispatching a ChangeMainViewEvent. You created a login view that took the values from a form and created a UserVO, which was passed to a LoginEvent, which was then dispatched. The visibility of several views and a setter function were bound
156
Chapter 16: User Login to the currentUser property of the ModelLocator to detect when a user had logged in. This binding was used to hide and show view elements and to clear the login form when a user successfully logged in. A welcome message was displayed to the user using the firstName property of the currentUser property of the ModelLocator, indicating that the user ’s account details are available in the application after login. The Logout button dispatched a LogoutEvent that cleared the user data (as evidenced by a change in the views bound to the currentUser property of the ModelLocator) and returned the user to the posts view.
157
Adding Posts In this chapter you will create the add posts feature. You will start by reviewing the requirements and then move on to creating the Cairngorm classes and views. The FlexBlogDatabaseManager class has been updated for this chapter. Please replace the current FlexBlogDatabaseManager.as file in the /src/sql/ folder with the one located in the source files for this chapter found in /Resources/code/ch17/src/sql/.
Adding Posts Over view The add posts feature must have the following features: ❑
The add posts view must be restricted to authors. Authors have an access level of 100.
❑
The posts area must be associated with categories and the author must be able to select a category when creating the posts. When the post is submitted, the associated category id must be submitted along with the post’s text.
❑
Authors should not be able to submit a post with a blank title or body.
❑
Once the author has clicked the submit button, it should be disabled until a result is returned so that the author cannot submit the post multiple times.
❑
If the adding of the post is successful, a message indicating this should be displayed.
❑
If the adding of the post is successful, the submit button should be enabled.
❑
If the adding of the post is successful, the submission form should be cleared.
❑
If the adding of the post is unsuccessful, a message informing the author should be displayed.
❑
If the adding of the post is unsuccessful, the form should remain populated so that the author does not have to type in the post again.
Chapter 17: Adding Posts You already have a button that provides access to the write view. You can restrict access to this view by detecting the current user ’s access level and, if it does not equal 100, hiding the button that provides access to the write view. Categories can be retrieved from the categories table of the database via a service in the FlexBlogDatabaseManager called getCategories. These can then be stored in a property in the ModelLocator and bound to a selection device such as a combo box in a view to allow the author to select a category. You can keep the author from submitting a blank post by using the validation classes that you used for the registration process. You can disable the submit button when the post is submitted. You can enable it by using a setter function that is bound to a property on the ModelLocator that indicates the status of the post submission. This is similar to what you did during the registration process. The same setter function that enables the submit button can be used to clear the registration form if the submission is successful. The UserNotificationVO can again be used to display messages to the author as necessary in the command classes.
Value Objects You will need value objects to represent posts and post categories. Starting with the post value object, the FlexBlogDatabaseManager uses a view in the database to retrieve posts. A view is just a collection of data from several tables that you can query without having to write the SQL to get the data from each table. This view contains all the columns found in the posts table, with some additional columns for retrieving such things as the author ’s name. Since the post value object will be used to both transfer data and display posts in the application, you will model it after the view. This will give you all the properties needed to both transfer data and display all the information returned from the post view. The posts view in the database has the following columns:
160
❑
postId
❑
authorId
❑
categoryId
❑
title
❑
body
❑
createdOn
❑
authorName
❑
categoryName
Chapter 17: Adding Posts In the com.FlexBlog.valueobjects package, create a new class named PostVO. Edit the class to match the following: package com.FlexBlog.valueobjects { public class PostVO { public var postId:int; public var authorId:int; public var categoryId:int; public var title:String; public var body:String; public var createdOn:String; public var authorName:String; public var categoryName:String; } }
Post categories are stored in the categories table of the database. This table consists of two columns: ❑
categoryId
❑
name
In the selection mechanism used by the author, you want to display the textual representation of the category found in the name property, but when the post is submitted, you need to pass along only the categoryId. Having a value object for categories will enable you to store the categoryId and name together as a single unit. You can then use a list-based component with the dataProvider bound to a collection of these value objects, and use the name property to display the names. The selectedItem property of the list can then be used to retrieve the associated categoryId. In the com.FlexBlog.valueobjects package, create a new class named CategoryVO. Edit the class to match the following: package com.FlexBlog.valueobjects { public class CategoryVO { public var categoryId:int; public var name:String; } }
Event Classes You will need an event class to retrieve the list of categories from the database and for the submission of posts.
161
Chapter 17: Adding Posts Starting with the event for loading the categories, in the com.FlexBlog.events package create a new event class named LoadCategoriesEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class LoadCategoriesEvent extends CairngormEvent { public static const LOAD:String = ‘loadCategoriesEvent’; public function LoadCategoriesEvent(bubbles:Boolean=false, cancelable:Boolean=false) { super(LOAD, bubbles, cancelable); } } }
This event does not need to pass along any data and is simply going to trigger the corresponding command class. Next, create the event for adding a post. In the com.FlexBlog.events package create a new event class named AddPostEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.control.CairngormEvent; public class AddPostEvent extends CairngormEvent { public static const ADD_POST:String = ‘addPostsevent’ public var post:PostVO; public function AddPostEvent(post:PostVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(ADD_POST, bubbles, cancelable); this.post = post; } } }
This event takes in a PostVO that will be passed to the corresponding command class.
Delegate Classes Since both the retrieval of the categories and the add post process will need to interact with the database, you will need a delegate class for both these procedures. Start with the category retrieval delegate. Create a new delegate class named LoadCategoriesDelegate in the com.FlexBlog.delegates package. Edit the class to match the following:
162
Chapter 17: Adding Posts package com.FlexBlog.delegates { import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class LoadCategoriesDelegate { private var responder:IResponder; public function LoadCategoriesDelegate(responder:IResponder) { this.responder = responder; } public function loadCategories():void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var categories:Array = dbManager.getCategories(); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{categories:categories}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The LoadCategoriesDelegate simply calls the getCategories method on the FlexBlogDatabaseManager. This method returns an array of CategoryVO objects. This array is then assigned to the categories property of the ResultEvent result property and passed back to the command class. If something goes wrong with the query, the fault function of the responder is called. Next, create the delegate class for adding posts. In the com.FlexBlog.delegates package create a new delegate class named AddPostDelegate. Edit it to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.PostVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class AddPostDelegate { private var responder:IResponder; public function AddPostDelegate(responder:IResponder) {
163
Chapter 17: Adding Posts this.responder = responder; } public function addPost(post:PostVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var added:int = dbManager.addPost(post); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:added}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The AddPostDelegate class takes in a PostVO and passes it to the addPost method of the FlexBlogDatabaseManager. This method returns an integer indicating the success of the add post process. The value is actually the number of rows inserted, which will be 1 upon success or 0 if nothing was inserted. This value is assigned to the added property of the result property of the ResultEvent and passed back to the command. If something goes wrong with the query, the fault function of the responder is called.
Command Classes You need a command class for the loading of the categories and for adding a post. Start with the command class for loading the categories. You will need to store the categories that are loaded in a property on the ModelLocator. Add the following to the FlexBlogModel: public var categories:ArrayCollection = new ArrayCollection();
Make sure that Flex has added the import for the ArrayCollection class. Next, in the com.FlexBlog.commands package create a new command class called LoadCategoriesCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.LoadCategoriesDelegate; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent;
164
Chapter 17: Adding Posts import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class LoadCategoriesCommand implements ICommand, IResponder { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { var delegate:LoadCategoriesDelegate = new LoadCategoriesDelegate(this); delegate.loadCategories() } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; var notification:UserNotificationVO = new UserNotificationVO(); if (evt.result.categories){ this.model.categories.source = evt.result.categories; } } public function fault(info:Object):void { } } }
The execute function simply calls the loadCategories method of the LoadCategoriesDelegate. The result function checks to see if any categories were returned and, if so, sets the source property of the categories ArrayCollection of the ModelLocator to the returned array. The fault function does not do anything as there is not much use in informing the user that the categories could not be loaded, but you can display a message using the UserNotificationVO, or handle the error in some other way. For now, leave this as it is. Now create the command for adding a post. This command will update a variable on the model indicating the status of the post submission. Add the following to the FlexBlogModel: public var postSubmissionStatus:String;
Next, in the com.FlexBlog.commands package create a new command class called AddPostCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.AddPostDelegate; import com.FlexBlog.events.AddPostEvent; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class AddPostCommand implements ICommand, IResponder {
165
Chapter 17: Adding Posts private var model:FlexBlogModel =FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { this.model.postSubmissionStatus =’’ var evt:AddPostEvent = event as AddPostEvent var delegate:AddPostDelegate = new AddPostDelegate(this); delegate.addPost(evt.post); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; var notification:UserNotificationVO = new UserNotificationVO(); if (evt.result.added == 1){ this.model.postSubmissionStatus = ‘success’; notification.message = ‘Post added’; }else{ this.model.postSubmissionStatus = ‘failed’; notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘Post could not be added’; } this.model.userNotification = notification; } public function fault(info:Object):void { this.model.postSubmissionStatus = ‘fault’; var notification:UserNotificationVO = new UserNotificationVO(); notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message =’The server encountered an error while trying to add the post.’ this.model.userNotification = notification; } } }
The execute method takes the PostVO of the AddPostEvent class and passes it to the addPost method of the AddPostDelegate. It also sets the postSubmissionStatus property of the ModelLocator to an empty string to indicate that submission is in progress. The result function uses the added property of the result object on the ResultEvent to determine if the post was added successfully. It uses this information to display either a success or failure message to the user using a UserNotificationVO, and to update the postSubmissionStatus property of the ModelLocator. The fault function alerts the user that something went wrong on the system level, again using a UserNotificationVO, and also updates the postSubmissionStatus property of the ModelLocator. Now that you have the corresponding event and command classes, add them to the FlexBlogController: addCommand(LoadCategoriesEvent.LOAD, LoadCategoriesCommand); addCommand(AddPostEvent.ADD_POST,AddPostCommand);
166
Chapter 17: Adding Posts
Views The view for editing a post is a simple form. The information that the user needs to provide is the post title, body, and category. All the additional information for creating the post can be retrieved from the currentUser property of the ModelLocator or known values. The authorId will always be the id of the current user and the createdOn date is always the current date, which is handled by the database. In the com.FlexBlog.views package create a new component named PostEditor, based on form. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
The submitPost method performs the validation that prevents the user from submitting a post with an empty title or body. If the post is invalid, an alert is displayed to the user indicating how to fix the invalid fields. If the post is valid, the submitPosts method creates a PostVO using the values from the form and the currentUser property of the ModelLocator. It then passes the PostVO to an AddPostEvent that is then dispatched. Additionally, the submitPost method disables the submitPostButton until a result is returned. The postSubmitted setter function will be bound to the postSubmissionStatus property of the ModelLocator. This function checks the value of the postSubmissionStatus. If the post was successfully submitted, the form is cleared. If the status is anything other than in progress (as indicated by an empty string), the submit button is enabled. If you move down to the form, you will see that there is a combo box:
This combo box is bound to the categories property of the ModelLocator. The categories property holds an ArrayCollection of CategoryVO objects. The label field of the combo box is set to the name property. This will allow the user to select a category by name. The categoryId can then be retrieved by referencing the selectedItem property of the combo box (as can be seen in the submitPost function). The submitPostButton simply triggers the submitPost function, which causes the validation routine to start and, if validation is passed, fires the AddPostEvent.
168
Chapter 17: Adding Posts
Implementation and Testing The first thing you need to take care of is restricting access to the write view. If you go to the write view without being logged in there will be no user id for the currentUser and the submitPost function will give you a null reference in error when trying to retrieve the userId to set it as the authorId for the PostVO. To restrict access to the write view, edit the writeButton in the ApplicationControlBar in the MainView component to match the following:
Here the visible property is tied to the value of a ternary expression that checks to see if there is a current user. Simply checking to see if accessLevel equals 100 will not work and will cause the button to be shown until a user logs in and there is a value for the accessLevel property, so you first have to determine whether the currentUser property is null. If there is no user, the button is hidden. If there is a user, the access level is checked and the button is displayed if the access level is 100 (indicating that the user is an author). Now edit the writeView HBox, adding the PostEditor view below the label:
The postSubmitted setter function is being bound to the postSubmissionStatus property of the ModelLocator so that the function is called when the status changes. You also need to trigger the loading of the categories. Since these categories will also be used by other features such as search, you will load them when the application loads. To do this, add the following function inside the Script tag of the MainView component: private function init():void{ new LoadCategoriesEvent().dispatch(); }
Make sure that Flex has added the import statement for the LoadCategoriesEvent class for you. Now edit the root VBox tag and bind the init function to the creationComplete event:
If you debug the application you should see something like Figure 17-1. The Write button in the ApplicationControlBar should be hidden.
169
Chapter 17: Adding Posts
Figure 17-1 The users table of the database has been pre-populated with several author-level users. Log in with the following user: ❑
Username = poe
❑
Password = password
After you log in, the Write button should be displayed, as in Figure 17-2.
Figure 17-2 Click the Write button and your screen should look like Figure 17-3.
Figure 17-3
You can see that the categories are being loaded and that the bindings are working by the fact that the combo box is being populated. To test that the basic validation is working (again, not all possible validation scenarios are going to be tested), try submitting a blank post. You should see an alert, as in Figure 17-4.
170
Chapter 17: Adding Posts
Figure 17-4
Close the dialog and enter some text for the title and body of the post. Submit the post and you should see something like Figure 17-5, with the success message being displayed at the upper left.
Figure 17-5
Again, the disabling of the submit button occurs so quickly that the most you will notice is a quick flash. However, as can be seen in Figure 17-5, the success message is being displayed and the form has been cleared. We will not worry about testing the fault conditions. The test you have performed shows that the basic add post procedure can be completed without any errors being thrown. The completed tests confirm the following: ❑
You cannot submit a post with a blank title or body.
❑
The success message is displayed when a post is submitted.
❑
The binding to the postSubmitted setter function is being updated by changes in the postSubmissionStatus property of the ModelLocator. You know this because the form is being cleared and the submit button has been re-enabled.
❑
The category loading classes work, as evidenced by the population of the combo box.
If you did not get the expected results, compare your code to the sample code found in /Resources /code/ch17/.
171
Chapter 17: Adding Posts
Summary In this chapter you created the add post feature. Let’s take a moment to review how you used Cairngorm to do it. You created PostVO and CategoryVO value object classes to represent posts and post categories. These, again, formed the basis for passing data between classes. Additionally, you used a collection of CategoryVO objects as the dataProvider for a combo box in your PostEditor view, allowing the user to select a category and enabling the categoryId to be retrieved by means of the selectedItem property of the combo box. You created LoadCategoriesEvent and AddPostEvent event classes. The LoadCategoriesEvent simply triggered the corresponding command class. You triggered this event in the main application by creating an init function that was triggered on the creationComplete event of the MainView component. The AddPostEvent took in a PostVO as a parameter and passed it on to the command class. You created LoadCategoriesDelegate and AddPostDelegate delegate classes. The LoadCategoriesDelegate called the getCategories method of the FlexBlogDatabaseManager, which returned an array of CategoryVO objects. These were then passed back to the command class via the result object of the ResultEvent. The AddPostDelegate addPost method took in a PostVO and passed it to the addPost method of the FlexBlogDatabaseManager, which indicated success by returning the number of rows inserted into the database. The number of rows was passed back to the command class via the result object of the ResultEvent so that the command could display the appropriate message. You created LoadCategoriesCommand and AddPostCommand command classes. LoadCategoriesCommand took the array of CategoryVO objects returned by the delegate and updated the source of an ArrayCollection named categories on the ModelLocator, making the CategoryVO objects available to the application. The AddPostCommand took the integer value passed back via the ResultEvent and used UserNotificationVO objects to tell the user about the status of the post submission. You created a PostEditor view that allows the user to enter the details for a post and submit it. When the form was submitted, if all the validation checks were passed, a PostVO object was constructed out of the values from the form and information from the currentUser property of the ModelLocator. This was then passed to an AddPostEvent, which was then dispatched. You also created a combo box that bound to a collection of CategoryVO objects on the ModelLocator to allow the user to select a category when submitting the post.
172
Loading Posts In this chapter you will create the load posts feature. You will start by reviewing the requirements and then move on to creating the Cairngorm classes and views. Additionally, during the course of building this feature you will take a look at revising some of the current features to make the design more flexible and better organized. The FlexBlogDatabaseManager class has been updated for this chapter. Please replace the current FlexBlogDatabaseManager.as file in the /src/sql/ folder with the one located in the source files for this chapter found in /Resources/code/ch18/src/sql/.
Loading Posts Over view The load posts feature has the following requirements: ❑
When users arrive at the application, they should be presented with a list of the most recent posts sorted by date. The post retrieval service will provide them in this order.
❑
The most recent post should be displayed automatically.
❑
When users click on one of the titles in the recent posts list, the full post should be displayed in place of the currently displayed post.
You can load posts by triggering the appropriate event when the application loads. You are already doing this for the loading of the categories. Since there are now a number of things that need to occur when the applications loads, you are going to centralize these things by creating an event and command class that will execute when the application loads. The command class that gets triggered when the application loads will then be responsible for triggering any events that need to occur. This will also make creating new main views simpler, since all these views have to do is dispatch a single event, rather than remember a list of events that need to be dispatched.
Chapter 18: Loading Posts You can display a full post by binding a view to a property on the model that holds a PostVO representing the currently selected post. Since the recent posts service returns the posts in the order of their creation dates, displaying the most recent post is simply a matter of taking the first item in the list of posts returned by the service and assigning it to the property on the model representing the current post. However, since the loading of the posts could potentially occur after the user has selected a post (say, if the user periodically reloads them, which you won’t be doing but which is a realistic possibility), this will only occur if the property on the model representing the current post is null. To allow the user to switch between posts, a list-based component can be used with all the PostVO objects. When a post is selected, an event will trigger a command to update a property of the ModelLocator from the list’s selectedItem.
Value Objects Here again you are working with posts and will simply be using the existing PostVO. No other value objects are required for the load posts feature.
Event Classes As mentioned in the overview, you will be consolidating code that needs to be executed when the application loads in an event class and a command class, so you will need an event that gets dispatched when the application loads. You will also need events to load the recent posts and to allow the user to set the currently selected posts. Start with the event that will be triggered when the application loads. In the com.FlexBlog.events package create a new event class named ApplicationInitializeEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class ApplicationInitializeEvent extends CairngormEvent { public static const INIT:String = ‘applicationInitEvent’; public function ApplicationInitializeEvent(bubbles:Boolean=false, cancelable:Boolean=false) { super(INIT, bubbles, cancelable); } } }
174
Chapter 18: Loading Posts This event simply triggers the corresponding command class and does not need to pass any data. Next, move on to the event for loading the posts. In the com.FlexBlog.events package create a new event class named LoadRecentPostsEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class LoadRecentPostsEvent extends CairngormEvent { public static const LOAD:String = ‘loadRecentPostsEvent’; public function LoadRecentPostsEvent(bubbles:Boolean=false, cancelable:Boolean=false) { super(LOAD, bubbles, cancelable); } } }
Again, this class is simply triggering the corresponding command class and no data needs to be passed. Finally, create the event for setting the currently displayed post. In the com.FlexBlog.events package create a new event class named SetCurrentPostEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.control.CairngormEvent; public class SetCurrentPostEvent extends CairngormEvent { public static const SET:String = ‘setCurrentPostsEvent’; public var post:PostVO; public function SetCurrentPostEvent(post:PostVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(SET, bubbles, cancelable); this.post = post; } } }
This event takes in a PostVO object that gets passed to the corresponding command class.
Delegate Classes Only the loading of the posts needs to interact with the database, so you need only a delegate for that process.
175
Chapter 18: Loading Posts In the com.FlexBlog.delegates package, create a new delegate class named LoadRecentPostsDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class LoadRecentPostsDelegate { private var responder:IResponder public function LoadRecentPostsDelegate(responder:IResponder) { this.responder = responder; } public function loadRecentPosts():void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.getRecentPosts(); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{posts:posts}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The loadRecentPosts method calls the getRecentPosts method of the FlexBlogDatabaseManager. The getRecentPosts method returns an array of PostVO objects. This array is passed back to the command class using a variable called posts on the result object of the ResultEvent. If something goes wrong with loading the posts, the fault method of the responder is called.
Command Classes You need command classes for application initialization, the loading of the posts, and setting the current post. Start with the application initialization command. In the com.FlexBlog.commands package create a new command class named ApplicationInitializeCommand. Edit it to match the following: package com.FlexBlog.commands { import com.FlexBlog.events.LoadCategoriesEvent; import com.FlexBlog.events.LoadRecentPostsEvent;
176
Chapter 18: Loading Posts import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; public class ApplicationInitializeCommand implements ICommand { public function execute(event:CairngormEvent):void { new LoadCategoriesEvent().dispatch(); new LoadRecentPostsEvent().dispatch(); } } }
Note that the execute function is dispatching the events for anything that needs to occur when the application loads. Thus far this includes loading the categories and recent posts. From now on, when anything needs to happen when the application first loads, you can add it to this command class and not have to worry about it in your main view. Next move on to the command class for loading posts. You need a property on the ModelLocator to represent the list of recent posts in addition to the property representing the currently selected post. Add the following to the FlexBlogModel: public var recentPosts:ArrayCollection = new ArrayCollection(); public var currentPost:PostVO;
Make sure Flex has added the import statements for you. Now create the command class for loading posts. In the com.FlexBlog.commands package create a new command class named LoadRecentPostsCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.LoadRecentPostsDelegate; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class LoadRecentPostsCommand implements ICommand, IResponder { private var model:FlexBlogModel =FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { var delegate:LoadRecentPostsDelegate = new LoadRecentPostsDelegate(this); delegate.loadRecentPosts(); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.posts){ this.model.recentPosts.source = evt.result.posts;
177
Chapter 18: Loading Posts if(!this.model.currentPost){ this.model.currentPost = PostVO(this.model.recentPosts.getItemAt(0)); } } } public function fault(info:Object):void { } } }
The execute method calls the loadRecentPosts method of the LoadRecentPostsDelegate. The result method checks to see if any posts were returned by the delegate. If posts have been returned, the source of the ArrayCollection representing the list of recent posts is updated with the returned results. Additionally, if no post is currently being displayed the current post is set to the first item in the returned results. Nothing is being done with the fault function at this time, but if you want you can use a UserNotificationVO to display a message to the user indicating that there was a problem loading the posts. Finally, move on to the command class for setting the current posts. In the com.FlexBlog.commands package create a new command class named SetCurrentPostCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.events.SetCurrentPostEvent; import com.FlexBlog.models.FlexBlogModel; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; public class SetCurrentPostCommand implements ICommand { private var model:FlexBlogModel = FlexBlogModel.getInstance() public function execute(event:CairngormEvent):void { var evt:SetCurrentPostEvent = event as SetCurrentPostEvent; this.model.currentPost = evt.post; } } }
This command takes the PostVO object passed in by the event class and updates the currentPost property of the ModelLocator.
178
Chapter 18: Loading Posts Now that you have all your events and command classes created, add them to the FlexBlogController: addCommand(ApplicationInitializeEvent.INIT,ApplicationInitializeCommand); addCommand(LoadRecentPostsEvent.LOAD, LoadRecentPostsCommand); addCommand(SetCurrentPostEvent.SET,SetCurrentPostCommand);
Views You need a view that will allow the user to select a post and a view to display the details of the selected post. Start with the view that will display the list of recent posts and allow the user to select posts. Then in the com.FlexBlog.views package create a new component based on List. Name it PostList. Remember to clear the height and width from the creation dialog box. Edit the component to match the following:
This component will receive a collection of PostVO objects by binding its dataProvider property to the recentPosts property of the ModelLocator. Note that the labelField property has been set to title, meaning that this list will display the titles from the PostVO collection. Further note that the itemClick property has been set to trigger a function named onPostSelected. The onPostSelected method takes the selectItem from the list and passes it to a SetCurrentPostEvent that is then dispatched. Now create the view for displaying a post. In the com.FlexBlog.views package create a new component based on VBox. Name it PostDisplay. Remember to clear the height and width from the creation dialog. Edit the component to match the following: .postTitle{ font-weight:bold; font-size:18px; } .byLine{ font-style:italic; font-size:16px; } .postBody{ font-size:12px; } .postDate{ font-style:italic; font-weight:bold; }
This component simply grabs a reference to the model and then binds several text fields to the properties of the PostVO object referenced by the currentPost property of the ModelLocator. Additionally, several styles are defined for displaying different parts of the post.
Implementation and Testing Since the logic for what needs to occur when the application first loads has been moved to an event and command class, you are going to have to make some modifications to the MainView component to trigger the ApplicationInitializationEvent. While we are in there making modifications, we’re going to make a few others regarding how views are being constructed to make the design more modular and organized. If you continued simply adding views to the main view, this file could potentially get quite large. Part of the reason for using views is that you can, ideally, arrange them so that each view contains only the code to make that view operate. This keeps your view files simple and easy to deal with and also has the added benefit of making code related to specific views easy to find and update. In order to keep the MainView component from getting too large, you are going to reorganize it by splitting it up into multiple views, which in turn will be made up of other views. Thus the MainView simply acts as a container that holds the sub-views. Adding another main view will simply be a matter of creating the sub-view component and adding it to the MainView as a direct child of the ViewStack component, and adding constants to the ChangeMainViewEvent to represent the new main view. First you are going to address the new method of handling application initialization. Edit the init function of the MainView to match the following:
180
Chapter 18: Loading Posts private function init():void{ new ApplicationInitializeEvent().dispatch(); }
Make sure that Flex adds the import statements for you. Now any code that needs to be executed when the application is initialized can be added to the ApplicationInitializeCommand and won’t be cluttering up your MainView. Next you are going to create views representing each of the main views: posts, register, and write. Starting with the posts view in the com.FlexBlog.views package, create a new component based on VBox. Name it PostsView. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
Make sure that you have added the views namespace to the root tag. Note that you have added the PostList view with the dataProvider bound to the recentPosts property of the ModelLocator and the PostDisplay view. Next create the view for the register main view. In the com.FlexBlog.views package create a new component based on VBox. Name it RegisterView. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
Make sure that you have added the views namespace to the root tag. Note that you have added the RegistrationForm view as it exists currently in the MainView. Now create the view for the write main view. In the com.FlexBlog.views package create a new component named WriteView based on VBox. Remember to clear the height and width from the creation dialog. Edit the component to match the following: < /mx:Script>
Make sure that you have added the views namespace to the root tag. Note that you have added the PostEditor view as it exists currently in the MainView. Now that you have components for the main views, edit the ViewStack of the MainView to match the following:
While you had to construct a few additional views, you can see that doing so keeps individual views small and manageable, since even with the minor changes made here the view stack looks much more organized. Now that the reorganization is done, you can move on to testing the loading of the posts. If you debug the application you should see something like Figure 18-1.
182
Chapter 18: Loading Posts
Figure 18-1
From this you can see that the new initialization procedure is working and that the load post classes are loading the list of posts and automatically displaying the most recent posts. Now click on one of the posts from the Recent Posts list and confirm that the displayed post changes. Finally, just to make sure nothing got broken in the view restructuring, log in with the poe account (user name = poe, password = password) so that the button for the write view is displayed. Then click each of the main views to confirm that each is still displayed. If you did not get the expected results, compare your code to the sample code found in /Resources/ code/ch18/.
Summary In this chapter you created the load posts feature and did a little restructuring of your views. Let’s take a moment to review how you used Cairngorm to create this feature and how you restructured the views. You used the existing PostVO class in the PostList view to display the titles of recent posts and to allow the user to select a post. You did this by updating the currentPost property of the ModelLocator with the selectedItem from the list. The PostDisplay view used the PostVO object referenced in the currentPost property of the ModelLocator to display the details of the selected post. You created ApplicationInitializeEvent, LoadRecentPostsEvent, and SetCurrentPostEvent event classes. The ApplicationInitializeEvent will now be triggered when the main application loads, keeping you from having to list all the events that need to be triggered in your main view. The LoadRecentPostsEvent event was triggered in the ApplicationInitializeEvent (along with the loading of the categories), causing the posts to be loaded when the application loaded. The SetCurrentPostEvent will be triggered in your PostList view when the user selects an item from the list. It will take the PostVO from selectedItem from the list and pass it to the SetCurrentPostCommand. You created a LoadRecentPostsDelegate that accessed the getRecentPosts method of the FlexBlogDatabaseManager, returning an array of PostVO objects that was passed back to the command via the result object of the ResultEvent.
183
Chapter 18: Loading Posts You created ApplicationInitializeCommand, LoadRecentPostsCommand, and SetCurrentPostCommand classes. The ApplicationInitializeCommand provided a central location for dispatching any events that need to occur when the application loads. The LoadRecentPostsCommand called the loadPosts method of the LoadRecentPostsDelegate. The result function updated the recentPosts property of the ModelLocator with the posts returned by the delegate and set the first post in the list to be displayed. The SetCurrentPostCommand took the PostVO passed in by the SetCurrentPostEvent and used it to update the currentPost property of the ModelLocator. You created PostList and PostDisplay views. The PostList view dataProvider property was bound to the recentPosts property of the ModelLocator. The list displayed the titles of the collection of PostVO objects in the dataProvider and dispatched a SetCurrentPostEvent, passing it the selectedItem from the list, allowing the user to change the currently displayed post. The PostDisplay used bindings to the currentPost property of the ModelLocator to display the information for that post. In addition to creating the classes and view for the loading of the posts, you restructured your views by creating components representing each of the main views. You created PostsView, RegisterView and WriteView components. These in turn were composed of other simple view components such as the PostList, PostDisplay, RegistrationForm, and PostEditor views. Your doing this allowed each of your views to remain lightweight and contain only the code necessary to function. Finally, note that the view restructuring did not require you to modify any of the code responsible for changing views.
184
Adding a Commenting System In this chapter you will create the commenting feature. You will start by reviewing the requirements and then move on to creating the Cairngorm classes and views. The FlexBlogDatabaseManager class has been updated for this chapter. Please replace the current FlexBlogDatabaseManager.as file in the /src/sql/ folder with the one located in the source files for this chapter found in /Resources/code/ch19/src/sql/.
Commenting Over view The commenting system has the following requirements: ❑
Any time the full details of a post are displayed (whether automatically when the application loads, or by user selection) any comments associated with that post should be displayed as well.
❑
If there are no comments associated with the current post, the comment list should be hidden.
❑
Users may not submit blank comments.
❑
Only logged-in users may comment, so the commenting form should be displayed only when there is a user logged in.
❑
When the user submits the comment, the submit button should be disabled until a result is returned.
❑
When a comment is successfully submitted, the comment form should be cleared and the submit button enabled.
Chapter 19: Adding a Commenting System Loading comments when a post is displayed is a matter of triggering the comment loading logic whenever a post is set as the current post. You can hide the comment display by tying the visibility of the view to the number of comments for the selected posts. You can keep users from submitting blank comments by using the Flex validation classes that you used for registration and the add posts feature. You can allow only logged-in users to post comments by tying the visibility of a comment to the user ’s being logged in. You can disable the submit button and clear the form by binding a setter function to a property that indicates the status of the comment submission.
Value Objects Since comments are associated with particular posts, you will be using the postId property of a post to load the associated comments. Which comments are displayed is determined by the currently selected post: therefore you can use the postId property of the PostVO referenced by the currentPosts property of the ModelLocator to load the associated comments. When submitting a comment you still need the postId, but you also need the body of the comment and the id of the user making the posts. So once again a comment can be seen as a collection of related data that needs to be passed around as a unit. Because of this you will create a value object to represent comments. The query, or the act of retrieving comments, returns the following columns: ❑
commentId
❑
postId
❑
userId
❑
body
❑
madeOn
❑
postedBy
These include all the columns from the comments table and the name of the person who made the comment contained in the postedBy property retrieved via a join in the SQL. In the com.FlexBlog.valueobjects package create a new value object class named CommentVO. Edit the class to match the following:
186
Chapter 19: Adding a Commenting System package com.FlexBlog.valueobjects { public class CommentVO { public var commentId:int public var postId:int public var userId:int; public var body:String; public var madeOn:String; public var postedBy:String; } }
Event Classes You need event classes both for loading comments and for adding a comment. Start with the event for loading comments. In the com.FlexBlog.events package create a new event class named LoadCommentsEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.control.CairngormEvent; public class LoadCommentsEvent extends CairngormEvent { public static const LOAD:String = ’loadCommentsEvent’; public var post:PostVO public function LoadCommentsEvent(post:PostVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(LOAD, bubbles, cancelable); this.post = post; } } }
This event takes in a PostVO that gets passed to the command class so that the postId property can be used to load the corresponding comments. Next, create the event for adding a comment. In the com.FlexBlog.events package create a new event class named AddCommentEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.CommentVO; import com.adobe.cairngorm.control.CairngormEvent; public class AddCommentEvent extends CairngormEvent { public static const ADD:String = ‘addCommentEvent’;
187
Chapter 19: Adding a Commenting System
public var comment:CommentVO; public function AddCommentEvent(comment:CommentVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(ADD, bubbles, cancelable); this.comment =comment; } } }
This event takes in a CommentVO object that gets passed to the command class so that the comment can be added to the database.
Delegate Classes Both the loading and the adding of comments require interaction with the database, so you will need delegate classes for both procedures. Start with the delegate for loading comments. In the com.FlexBlog.delegates package create a new delegate class named LoadCommentsDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.PostVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class LoadCommentsDelegate { private var responder:IResponder; public function LoadCommentsDelegate(responder:IResponder) { this.responder = responder; } public function loadComments(post:PostVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var comments:Array = dbManager.getComments(post); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{comments:comments}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
188
Chapter 19: Adding a Commenting System The loadComments function calls the getComments function of the FlexBlogDatabaseManager, which returns an array of CommentVO objects. This array is passed back to the command class in the comments property of the result object of the ResultEvent. If something goes wrong while the comments are being retrieved, the fault function of the responder is called. Next, create the delegate for adding a comment. In the com.FlexBlog.delegates package create a new delegate class named AddCommentDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.CommentVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class AddCommentDelegate { private var responder:IResponder; public function AddCommentDelegate(responder:IResponder) { this.responder = responder; } public function addComment(comment:CommentVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var added:int = dbManager.addComment(comment); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:added}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The addComment function calls the addComment function of the FlexBlogDatabaseManager, which returns an integer representing the number of rows inserted, indicating the comment was added successfully. This integer is passed back to the command class in the added property of the result object of the Result event. If something goes wrong while the comment is being added, the fault function of the responder is called.
Command Classes You need command classes for both loading and adding comments.
189
Chapter 19: Adding a Commenting System Start with the command for loading comments. You need a property on the model to represent the comments loaded by the command class. Add the following to the FlexBlogModel: public var comments:ArrayCollection =new ArrayCollection();
Now create the command class. In the com.FlexBlog.commands package create a new command class named LoadCommentsCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.LoadCommentsDelegate; import com.FlexBlog.events.LoadCommentsEvent; import com.FlexBlog.models.FlexBlogModel; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class LoadCommentsCommand implements ICommand, IResponder { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { var evt:LoadCommentsEvent = event as LoadCommentsEvent; var delegate:LoadCommentsDelegate = new LoadCommentsDelegate(this); delegate.loadComments(evt.post); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; this.model.comments.source = evt.result.comments; } public function fault(info:Object):void { } } }
The execute function calls the loadComments function of the LoadCommentsDelegate, passing it the PostVO object from the LoadCommentsEvent event. Using the comments property of the result object of the ResultEvent, the result function updates the comments ArrayCollection on the ModelLocator with the comments returned by the delegate. Nothing is being done in the fault handler method, but if you want, you can display a message to the user using the UserNotificationVO class. Next, create the command for adding a comment. This command will be updating a submission status flag on the model as was done for registration and the adding of posts. Add the following to the FlexBlogModel: public var commentSubmissionStatus:String;
190
Chapter 19: Adding a Commenting System Now create the command class. In the com.FlexBlog.commands package create a new command class named AddCommentCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.AddCommentDelegate; import com.FlexBlog.events.AddCommentEvent; import com.FlexBlog.events.LoadCommentsEvent; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class AddCommentCommand implements ICommand, IResponder { private var model:FlexBlogModel =FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { this.model.commentSubmissionStatus =’’ var evt:AddCommentEvent = event as AddCommentEvent; var delegate:AddCommentDelegate = new AddCommentDelegate(this); delegate.addComment(evt.comment); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.added == 1){ this.model.commentSubmissionStatus =’success’ new LoadCommentsEvent(this.model.currentPost).dispatch(); } } public function fault(info:Object):void { this.model.commentSubmissionStatus =’fault’ var notification:UserNotificationVO =new UserNotificationVO(); notification.type =UserNotificationVO.ERROR_MESSAGE; notification.message=’There was a server error while trying to add your comment.’; this.model.userNotification =notification; } } }
The execute function sets the commentSubmissionStatus property of the ModelLocator to an empty string to indicate that submission is in progress. It then calls the addComment function of the AddCommentDelegate class with the CommentVO property of the AddCommentEvent as the argument.The result function uses the added property of the result object of the ResultEvent to determine if the comment was successfully added. If it was, the commentSubmissionStatus property of the ModelLocator is updated to indicate success and a LoadCommentsEvent is dispatched so that the newly added comment will be displayed. The fault function updates the commentSubmissionStatus property of the ModelLocator, setting it to fault, and uses a UserNotificationVO object to display a message to the user that something went wrong during the submission process.
191
Chapter 19: Adding a Commenting System Now that you have the corresponding event and command classes created, add them to the FrontController. Add the following to the FlexBlogController: addCommand(LoadCommentsEvent.LOAD,LoadCommentsCommand); addCommand(AddCommentEvent.ADD,AddCommentCommand);
Views You need views that will allow the user to enter comments and display the comments for the currently selected posts. You will continue the practice, started in Chapter 18 on loading posts, of breaking views down into distinct components. Start by creating the view that will allow users to enter comments. In the com.FlexBlog.views package create a new component based on Form. Name it CommentForm. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
192
Chapter 19: Adding a Commenting System .labelStyle{ font-weight:bold; }
Note that the visible property of the root form tag has been bound to an expression checking to see if the currentUser property of the ModelLocator is null. This has the effect of hiding the form when no user is logged in. The submitComment function performs the validation that prevents the user from submitting a blank comment. If the validation procedure is passed, the submit button is disabled and a new CommentVO object is constructed using the information from the form, the userId of the currentUser property of the ModelLocator, and the postId of the currentPost property of the ModelLocator. This CommentVO is then passed to an AddCommentEvent that is then dispatched. The commentSubmitted setter function will be bound to the commentSubmissionStatus property of the ModelLocator. If the submission was successful the form is cleared. If the status is anything but in progress, the submit button is enabled. The submitCommentButton simply triggers the submitComment function. The view that will display the comments will be made up of a list-based view that uses a custom itemRenderer. Start by creating the itemRenderer view. In the com.FlexBlog.views package create a new component based on VBox. Name it CommentDisplay. Remember to clear the height and width from the creation dialog. Edit the component to match the following: .commentByline{ font-style:italic; font-size:14px; }
193
Chapter 19: Adding a Commenting System ItemRenderers are passed a data object that represents the data they are intended to display. In this case, that will be a CommentVO. This component uses the properties of the CommentVO to display the name of the user who made the comment, the date the comment was made, and the comment itself. Now that you have the item rendered, create the list component that will use this itemRenderer to display the comments. In the com.FlexBlog.views package create a new component based on List. Name it CommentList. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
This component simply consists of a list with the itemRenderer property set to the CommentDisplay component that you created and with the selectable property false, since the user does not need to be able to trigger anything by selecting the comment. Finally, create the view that will contain the components you have created so far. In the com.FlexBlog. views package create a new component based on VBox. Name it CommentView. Remember to clear the height and width from the creation dialog. Edit the component to match the following: 0}” backgroundAlpha=”0” rowHeight=”150” />
This component consists of the CommentForm and CommentList components that you created. The commentSubmitted setter function of the CommentForm is bound to the commentSubmissionStatus property of the ModelLocator. The dataProvider for the CommentList is bound to the comments property of the ModelLocator. The visible property of the CommentList is bound to an expression that checks the length of the comments ArrayCollection against the number zero. If the comments ArrayCollection has at least one item in it, then the condition will be true and the item will display. Otherwise, the condition will be false and the item will not display.
194
Chapter 19: Adding a Commenting System
Implementation and Testing Since you want comments to be loaded whenever a post is selected, you need to trigger the LoadCommentsEvent whenever this occurs. Since you already have a command class for setting the current posts, you can dispatch the LoadCommentsEvent there. Edit the execute function of the SetCurrentPostCommand to match the following: public function execute(event:CairngormEvent):void { var evt:SetCurrentPostEvent = event as SetCurrentPostEvent; this.model.currentPost = evt.post; new LoadCommentsEvent(this.model.currentPost).dispatch(); }
Make sure that Flex has added the import statements for you. Now, whenever a post is selected, the associated comments will be loaded as well. However, you’ll recall that when you created the LoadRecentPostsCommand, you directly set the currentPost property of the ModelLocator. This would not cause the LoadCommentsEvent in the SetCurrentPostEvent to be triggered. Having the LoadRecentPostsCommand dispatch a SetCurrentPostEvent rather than directly updating the ModelLocator can fix this. Edit the result function of the LoadRecentPostsCommand to match the following: public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.posts){ this.model.recentPosts.source = evt.result.posts; if(!this.model.currentPost){ new SetCurrentPostEvent(PostVO(this.model.recentPosts.getItemAt(0))).dispatch(); } } }
Make sure that Flex has added the import statements for you. Now that the SetCurrentPostEvent is being used, the comments will be loaded when the recent posts are loaded when the application loads. Now you need to add the views that you created to the application. Since comments are intended to be displayed with posts, you will add these to the PostView. Edit the VBox currently containing the PostDisplay to include the CommentView, as in the following:
195
Chapter 19: Adding a Commenting System If you debug the application now, it should look like Figure 19-1.
Figure 19-1
Note that the CommentList is not being displayed because there are no comments associated with the current posts. Additionally, the CommentForm is hidden since there is no logged-in user. Log in with the poe account (user name = poe, password = password) and your screen should now look like Figure 19-2.
Figure 19-2
Now enter a comment in the form and submit it. Your screen should look like Figure 19-3, with your comment at the bottom.
196
Chapter 19: Adding a Commenting System
Figure 19-3
Now select another post from the Recent Posts list and confirm that the comment you just added is no longer displayed (your screen should look like Figure 19-2 with the details of the post you selected). Then reselect the first post in the list and confirm that the comment is redisplayed (again, you should see something like Figure 19-3).
Figure 19-4
Now that you have a comment in the system, confirm that comments load when the application loads by closing the application and launching it in debug mode again. Your screen should look like Figure 19-4 (comment displayed, but no comment form since there is no logged-in user). The test you performed confirms the following: ❑
The comment form remains hidden until a user logs in.
❑
The comment list does not display if there are no comments for the selected post.
❑
You can add a comment using the comment form.
❑
The comment form clears itself upon successful submission and the submit button becomes enabled.
❑
Comments are being loaded when you add a new comment or select a new post, or when the application loads.
197
Chapter 19: Adding a Commenting System
Summary In this chapter you created the login system. Let’s take a moment to review how you used Cairngorm to do it. You created a new value object class named CommentVO to represent comments and used the existing PostVO. The CommentVO class is used for adding comments and for displaying comments in the CommentList via the CommentDisplay itemRenderer. The existing PostVO class was used to load comments for the currently selected post by using the postId property of the currentPost property of the ModelLocator. You created LoadCommentsEvent and AddCommentEvent event classes. The LoadCommentsEvent took in a PostVO (in this case the one referenced by the the currentPost property of the ModelLocator) that was passed to the LoadCommentsCommand class so that the postId property could be used to retrieve comments associated with that post. The AddCommentEvent took in a CommentVO that was passed to the AddCommentCommand class so that the data could be passed to the command class and added to the database. You created LoadCommentsDelegate and AddCommentDelegate delegate classes. The loadComments function of the LoadCommentsDelegate class took in PostVO as a parameter. It then called the getComments function of the FlexBlogDatabaseManager, passing it the PostVO, which returned an array of PostVO objects. This array of PostVO objects was passed back to the LoadCommentsCommand class on the post property of the result object of the ResultEvent. The addComment function of the AddCommentDelegate class took in a CommentVO as a parameter. It then called the addComment function, passing it the CommentVO, which returned an integer indicating the successful addition of the comment. This integer was passed back to the command class on the added property of the result object of the ResultEvent. You created LoadCommentsCommand and AddCommentCommand command classes. The LoadCommentsCommand execute function called the loadComments function of the LoadCommentsDelegate, passing it the PostVO object from the LoadCommentsEvent event. The result function updated the comment property of the ModelLocator with the comments returned on the comments property of the result object of the ResultEvent. The AddCommentCommand execute function called the addComment function of the AddCommentDelegate, passing it the PostVO from the AddCommentEvent event. Additionally, it set the commentSubmissionStatus property of the ModelLocator to an empty string to indicate that submission was in progress. The result function checked to see if the comment was successfully added, using the added property of the result object of the ResultEvent. If the comment was successfully added, the commentSubmissionStatus property of the ModelLocator was updated to indicate success and a LoadCommentsEvent was dispatched to update the CommentList view. The fault function updated the commentSubmissionStatus property of the ModelLocator to fault and used the UserNotificationVO class to display a notification to the user that something went wrong. You also updated the SetCurrentPostCommand and LoadRecentPostsCommand classes. Since comments need to be loaded any time a post is selected, you modified the execute command of the SetCurrentPostCommand to dispatch a LoadCommentsEvent. You then changed the methodology used to set the initially displayed post in the LoadRecentPostsCommand execute function to dispatch a SetCurrentPostEvent rather than directly updating the ModelLocator.
198
Chapter 19: Adding a Commenting System You again broke your views down into discrete components, creating CommentView, CommentForm, CommentList, and CommentDisplay components. The CommentView components consisted of your CommentForm and CommentList components and set the appropriate bindings on them. The CommentList component simply consisted of a list that used the CommentDisplay components you created as its itemRenderer. The CommentForm component performed the validation required for submitting a comment and, if validation was successful, created a CommentVO using information from the form and the currentUser and currentPost properties of the ModelLocator. This CommentVO was then passed to an AddCommentEvent, which was then dispatched.
199
Adding Search Capabilities In this chapter you will create the search feature. You will start by reviewing the requirements and then move on to creating the Cairngorm classes and views. The FlexBlogDatabaseManager class has been updated for this chapter. Please replace the current FlexBlogDatabaseManager.as file in the /src/sql/ folder with the one located in the source files for this chapter found in /Resources/code/ch20/src/sql/.
Searching Over view You will be creating two types of search capabilities. The first is a keyword search. You perform this type of search by sending a list of keywords containing the search terms separated by spaces. The second is a category search. You perform this type of search by sending a category id to the search service, which will return all posts in that category. Both types of searches have the following requirements: ❑
They must return a list of post titles matching the search results, which the user can use to select a post.
❑
It should be clear to the user what type of search results he or she is viewing.
❑
There must be a mechanism to allow the user to clear the search results and hide the view.
Executing a keyword search is a matter of passing the search string to the Cairngorm classes responsible for performing the search. All you need to do is provide the user with a method of specifying keywords and take these and pass them to your Cairngorm classes.
Chapter 20: Adding Search Capabilities The category search requires that the user once again be able to select a category and that the categoryId be retrieved based on that selection. You have already done something similar for the add post feature. This categoryId will then be passed on to your Cairngorm classes to perform the search. Clearing the search results is a matter of removing the items from the property on the model representing the search results and hiding the search results view.
Value Objects Retrieving posts by category involves passing the categoryId so that posts with that categoryId can be retrieved. This property is already represented on the CategoryVO, so you can use the existing value object to do this. Keyword searches require only the string containing the search terms to be sent. Because of this there is no real collection of related data to be passed, only a single string. Creating a search value object to pass this string would be overkill (at least for the way it is being done in the sample application).
Event Classes You will need event classes for both types of search and one additional event class to clear the search results. Start with the event for the keyword search. In the com.FlexBlog.events package create a new event class named KeyWordSearchEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class KeyWordSearchEvent extends CairngormEvent { public static const KEYWORD_SEARCH:String = ‘keyWordSearchEvent’; public var keyWords:String; public function KeyWordSearchEvent(keyWords:String, bubbles:Boolean=false, cancelable:Boolean=false) { super(KEYWORD_SEARCH, bubbles, cancelable); this.keyWords = keyWords; } } }
This event simply takes in a string containing the keywords to use in the search so that they can be passed to the command class.
202
Chapter 20: Adding Search Capabilities Next, create the event for the category search. In the com.FlexBlog.events package create a new event class named CategorySearchEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.CategoryVO; import com.adobe.cairngorm.control.CairngormEvent; public class CategorySearchEvent extends CairngormEvent { public static const CATEGORY_SEARCH:String = ‘categorySearchEvent’; public var category:CategoryVO; public function CategorySearchEvent(category:CategoryVO, bubbles:Boolean=false, cancelable:Boolean=false) { super(CATEGORY_SEARCH, bubbles, cancelable); this.category = category; } } }
This event takes in a CategoryVO that gets passed to the command class so that the categoryId property can be used in the search. Finally, create the event for clearing search results. In the com.FlexBlog.events package create a new event class named ClearSearchResultsEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class ClearSearchResultsEvent extends CairngormEvent { public static const CLEAR_RESULTS:String = “clearSearchResultsEvent”; public function ClearSearchResultsEvent(bubbles:Boolean=false, cancelable:Boolean=false) { super(CLEAR_RESULTS, bubbles, cancelable); } } }
This event does not need to pass any data; it need only trigger the corresponding command class.
Delegate Classes Both types of searches require interaction with the database, so you will need delegate classes for both.
203
Chapter 20: Adding Search Capabilities Start with the delegate class for the keyword search. In the com.FlexBlog.delegates package create a new delegate class named KeyWordSearchDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class KeyWordSearchDelegate { private var responder:IResponder public function KeyWordSearchDelegate(responder:IResponder) { this.responder = responder; } public function search(keyWords:String):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.keyWordSearch(keyWords); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{posts:posts}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The search function takes in a string of keywords and calls the keyWordSearch function of the FlexBlogDatabaseManager, passing it the keywords, which returns an array of PostVO objects. This array is passed back to the command class in the posts property of the result object of the ResultEvent. If something goes wrong during the search, the fault function of the responder is called. Next, create the delegate class for the category search. In the com.FlexBlog.delegates package create a new delegate class named CategorySearchDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.CategoryVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent;
204
Chapter 20: Adding Search Capabilities import sql.FlexBlogDatabaseManager; public class CategorySearchDelegate { private var responder:IResponder public function CategorySearchDelegate(responder:IResponder) { this.responder = responder } public function search(category:CategoryVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.getPostsByCategory(category); public function getPostsByCategory(category:int):Array{ return this.query(“SELECT * FROM posts WHERE categoryId=”+category+”;”). data; } var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{posts:posts}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
The search function takes in a CategoryVO object and calls the getPostsByCategory function of the FlexBlogDatabaseManager, passing it the CategoryVO, which returns an array of PostVO objects. This array is passed back to the command class on the post property of the result object of the ResultEvent. If something goes wrong, the fault function of the responder is called.
Command Classes You need command classes for the keyword search and category search, and for clearing the search results. Your search classes will need properties on the ModelLocator to represent the search title displayed to the user, and to hold the collection of posts returned by the search functions. Add the following to the FlexBlogModel: public var searchTitle:String; public var searchResults:ArrayCollection = new ArrayCollection();
205
Chapter 20: Adding Search Capabilities Now create the command for the keyword search. In the com.FlexBlog.commands package create a new command class named KeyWordSearchCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.KeyWordSearchDelegate; import com.FlexBlog.events.KeyWordSearchEvent; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class KeyWordSearchCommand implements ICommand, IResponder { private var model:FlexBlogModel = FlexBlogModel.getInstance(); private var keyWords:String; public function execute(event:CairngormEvent):void { this.model.searchTitle = “Searching ...” var evt:KeyWordSearchEvent = event as KeyWordSearchEvent; this.keyWords = evt.keyWords; var delegate:KeyWordSearchDelegate = new KeyWordSearchDelegate(this); delegate.search(this.keyWords); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.posts){ this.model.searchResults.source = evt.result.posts; this.model.searchTitle = “Search results for “ + this.keyWords + “.”; }else{ var notification:UserNotificationVO =new UserNotificationVO(); notification.message = “No results found for search “ + this.keyWords + “.”; this.model.userNotification = notification; } } public function fault(info:Object):void { var notification:UserNotificationVO = new UserNotificationVO(); notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message=”An error occured while performing your search.”; this.model.userNotification =notification; } } }
Note that this command has a private variable, keywords. This variable stores the keywords used in the search so that they can be displayed in the searchTitle property on the ModelLocator in the result function.
206
Chapter 20: Adding Search Capabilities The execute function sets the searchTitle property of the ModelLocator to “Searching…” to indicate that the search is in progress. It then calls the search function on the KeyWordSearchDelegate, passing it the keywords from the KeyWordSearchEvent event. The result function uses the posts property of the result object of the ResultEvent to determine if any posts were returned by the search. If results are found, the searchResults and searchTitle properties of the ModelLocator are updated. If no results are found, a message is displayed to the user by means of a UserNotificationVO. Since the visibility property of the search result view is bound to the length of the items in the searchResults property of the ModelLocator, the search results view will not display if there are no results. The user will not see any notifications in this view when there are no results. The fault function uses the UserNotificationVO class to notify the user that something went wrong during the search. Next, create the command for the category search. In the com.FlexBlog.commands package create a new command class named CategorySearchCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.delegates.CategorySearchDelegate; import com.FlexBlog.events.CategorySearchEvent; import com.FlexBlog.models.FlexBlogModel; import com.FlexBlog.valueobjects.CategoryVO; import com.FlexBlog.valueobjects.UserNotificationVO; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; import mx.rpc.events.ResultEvent; public class CategorySearchCommand implements ICommand, IResponder { private var model:FlexBlogModel = FlexBlogModel.getInstance(); private var category:CategoryVO public function execute(event:CairngormEvent):void { var evt:CategorySearchEvent = event as CategorySearchEvent; this.category = evt.category; var delegate:CategorySearchDelegate = new CategorySearchDelegate(this); delegate.search(this.category); } public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.posts){ this.model.searchResults.source = evt.result.posts; this.model.searchTitle = “Search results for category “ + this.category.name + “.”; }else{ var notification:UserNotificationVO =new UserNotificationVO(); notification.message = “No results found for category “ +
207
Chapter 20: Adding Search Capabilities this.category.name + “.”; this.model.userNotification = notification; } } public function fault(info:Object):void { var notification:UserNotificationVO = new UserNotificationVO(); notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message=”An error occurred while performing your search.”; this.model.userNotification =notification; } } }
Note here again that there is a private variable that will be used to update the searchTitle property of the ModelLocator in the result function. In this case it holds a reference to the CategoryVO passed in by the CategorySearchEvent. The logic of the result and fault functions of the CategorySearchCommand is essentially the same as that of the KeyWordSearchCommand, the major difference being that for the CategorySearchCommand a CategoryVO is being used to construct the strings for the searchTitle property of the ModelLocator in the result function. Finally, create the command class for the clearing of search results. In the com.FlexBlog.commands package create a new command class named ClearSearchResultsCommand. Edit the class to match the following: package com.FlexBlog.commands { import com.FlexBlog.models.FlexBlogModel; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; public class ClearSearchResultsCommand implements ICommand { private var model:FlexBlogModel = FlexBlogModel.getInstance(); public function execute(event:CairngormEvent):void { this.model.searchResults.removeAll(); this.model.searchTitle = ‘‘; } } }
This command simply updates the searchResults and searchTitle properties of the ModelLocator, removing all the items from the search results and setting the title to an empty string. Now that you have the corresponding events and command classes created, add them to the FrontController. Add the following to the FlexBlogController:
208
Chapter 20: Adding Search Capabilities addCommand(KeyWordSearchEvent.KEYWORD_SEARCH, KeyWordSearchCommand) addCommand(CategorySearchEvent.CATEGORY_SEARCH, CategorySearchCommand); addCommand(ClearSearchResultsEvent.CLEAR_RESULTS, ClearSearchResultsCommand);
Views You need views to allow the user to enter search terms, select categories, and display the results of searches in order to select posts from those results. Start with the view that will allow users to enter search terms. In the com.FlexBlog.views package create a new component based on HBox. Name it SearchBoxView. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
This component simply provides a textbox for the user to enter in terms and a button that directly dispatches a KeyWordSearchEvent using the value from the textbox. Next, create the view that will allow the user to search by category. In the com.FlexBlog.views package create a new component based on VBox. Name it CategorySearchView. Remember to clear the height and width from the creation dialog. Edit the component to match the following:
This component contains a list whose dataProvider property is bound to the categories property of the ModelLocator. The itemClick event on the list dispatches a new CategorySearchEvent using the selectedItem from the list cast as a CategoryVO. Finally, create the view that will display the search results and allow the user to select a post from those results. In the com.FlexBlog.views package create a new component based on VBox. Name it SearchResultsView. Remember to clear the height and width from the creation dialog. Edit the component to match the following: 0}” includeInLayout=”{this.model.searchResults.length >0}”>
Note that the visibility and includeInLayout properties of the root VBox tag have been bound to an expression that checks the length of the searchResults property of the ModelLocator. This has the effect of showing the view only if there are search results, and causes the view to take up no space in the layout if there are no search results. There is a label whose text property is bound to the searchTitle property of the ModelLocator to inform the user which type of search he or she performed. Next to this label is a button that dispatches a ClearSearchResultsEvent, thereby hiding the view. Finally, notice that you are reusing the PostList view that you created earlier. This view is already set up to set the current post being displayed by dispatching a SetCurrentPostEvent. Here you are simply binding the dataProvider to the searchResults property of the ModelLocator.
210
Chapter 20: Adding Search Capabilities
Implementation and Testing Since searching is related to posts, you will add the views that you created to the PostView component. Edit the VBox that currently contains the PostList that displays the recent posts, adding the SearchBoxView and CategorySearchView, as in the following:
Now edit the VBox that currently contains the PostDisplay and CommentView, adding the SearchResultsView, as in the following:
If you debug the application, it should look like Figure 20-1.
Figure 20-1
First do a keyword search that will not bring up any results. Search for “0000” (those are zeros not the letter o). You should see a notification, like the one at upper left in Figure 20-2.
211
Chapter 20: Adding Search Capabilities
Figure 20-2
Clear the notification and search for something that will bring up a result, such as “china” in the results window at left. Your screen should now look like Figure 20-3.
Figure 20-3
However, the term in the results window is only the one word, “china.” Click the post in the search results and confirm that it is displayed in full, as in Figure 20-4.
212
Chapter 20: Adding Search Capabilities
Figure 20-4
Now click the Clear button and confirm that the search results view hides itself (Figure 20-5).
Figure 20-5
Now select the Politics category from the Categories list. Your screen should now look like Figure 20-6.
Figure 20-6
Note that with the category search, the search title tells you what category you searched for instead of showing specific terms.
213
Chapter 20: Adding Search Capabilities You have already confirmed that selecting a post from the search results updates the displayed post and that the Clear button works, so there is no need to repeat those tests. The test you performed confirmed the following: ❑
When a keyword search is performed and no results are found, a notification is displayed to the user.
❑
When either a keyword or category search finds results, the SearchResultsView is displayed.
❑
Selecting a post from the SearchResultsView updates the currently displayed post.
❑
The Clear button in the SearchResultsView hides the SearchResultsView.
If you did not get the expected results, compare your code to the sample code found in /Resources /code/ch20/.
Summary In this chapter you created the search system, consisting of keyword searching and category searching. Let’s take a moment to review how you used Cairngorm to do it. You used the existing CategoryVO to perform category-based searches. You did this by using the existing categories property of the ModelLocator as the data provider of a list-based component. When the user selected an item from this list, the CategoryVO held in the selectedItem property of the list was passed to a CategorySearchEvent that was then dispatched. The categoryId property of the CategoryVO was then used to find posts in the database with that categoryId. The keyword search required only that a string containing the search terms be passed, and so did not warrant the creation of a value object. You created KeyWordSearchEvent, CategorySearchEvent, and ClearSearchResultsEvent event classes. The KeyWordSearchEvent took in a string of search terms and passed them along to the KeyWordSearchCommand class. The CategorySearchEvent took in a CategoryVO object and passed it along to the CategorySearchCommand class. The ClearSearchResultsEvent did not need to pass any data and simply triggered the ClearSearchResultsCommand. You created KeyWordSearchDelegate and CategorySearchDelegate delegate classes. The search function of the KeyWordSearchDelegate took in the keywords passed in as arguments by the KeyWordSearchCommand class and called the keywordSearch function of the FlexBlogDatabaseManager, passing it the keywords. The keywordSearch function of the FlexBlogDatabaseManager returned an array of PostVO objects that was passed back to the command class on the post property of the result object of the ResultEvent. The CategorySearchDelegate took the CategoryVO passed in by the CategorySearchCommand class and called the getPostsByCategory function of the FlexBlogDatabaseManager, passing it the CategoryVO. The getPostsByCategory function of the FlexBlogDatabaseManager used the categoryId property of the CategoryVO to find posts with that categoryId in the database and returned an array of PostVO objects that was passed back to the command class in the posts property of the result object of the ResultEvent.
214
Chapter 20: Adding Search Capabilities You created KeyWordSearchCommand, CategorySearchCommand, and ClearSearchResultsCommand command classes. The KeyWordSearchCommand execute function called the search function of the KeyWordSearchDelegate class, passing it the keywords from the KeyWordSearchEvent. The CategorySearchCommand execute function called the search function of the CategorySearchDelegate, passing it the CategoryVO from the CategorySearchEvent. The result and fault functions of the KeyWordSearchCommand and CategorySearchCommand classes essentially behaved the same way. The result function checked to see if any posts were returned and, if so, updated the searchTitle and searchResults properties of the ModelLocator. If no posts were returned, a notification was displayed to the user by means of the UserNotificationVO. The fault function used the UserNotificationVO class to display a message to the user that something went wrong during the search procedure. The execute function of the ClearSearchResultsCommand simply updated the searchResults and searchTitle properties of the ModelLocator, setting the searchTitle to an empty string and removing all the posts from the searchResults. You created SearchBoxView, CategorySearchView, and SearchResultsView views. This allowed the user to enter search terms and pass these terms to a KeyWordSearchEvent that was then dispatched. The CategorySearchView used a List component bound to the categories property of the ModelLocator. When the user selected an item from the list, the selectedItem from the list was passed to a CategorySearchEvent that was then dispatched. The SearchResultsView bound to the searchTitle property of the ModelLocator told the user what search results were being looked at. It used a PostList component with the dataProvider property bound to the searchResults property of the ModelLocator to allow the user to select a post from the search results. You added all these views to the existing PostView.
215
Reviewing Version One You have now created all the features for the Miss Information Media client that you are responsible for. In this chapter you’ll review the features of the application that you have created and examine the methodologies that you used. After this analysis you will be introduced to some alternative ways the application features could be built, in preparation for subsequent chapters, in which you will be revising your code to use these alternative methods.
The Application So Far Thus far you have created the following features in the sample application: ❑
Main application file (for testing other components and to hold a reference to the FrontController)
❑
User registration
❑
Login and logout
❑
Adding of new posts
❑
Loading of existing posts
❑
Commenting system
❑
Search capabilities (keyword and category).
Chapter 21: Reviewing Version One While the particular feature has varied for each chapter, the basic process for building it has been essentially the same each time:
1.
You identified and created any value objects for situations in which collections of data needed to be passed around the application or to the backend.
2.
You created event classes to trigger the logic for the feature. If data needed to be passed to commands, you commonly used value objects that could be passed in via the constructor and stored in public variables.
3. 4. 5.
You created delegate classes for procedures that required interaction with the backend.
6.
When you finished creating corresponding event and command classes, you added them to the FrontController.
7.
You created views that collected the necessary data expected by the event classes (often this involved constructing value objects) and then dispatched the appropriate events.
You added properties to the ModelLocator that your command classes would update. You created corresponding command classes for each event. These used the delegate classes to interact with the backend.
The logic behind this order is that you tend first to create objects that are referenced by other objects. For example, value objects were often used as parameters in event classes (and other classes as well), so by creating them first, you took advantage of the automatic importing and code-hinting features of Flex Builder. After completing the first few features, you probably could anticipate how future features were going to the built. I am talking not only about the prescribed order that was used (which is on some level arbitrary; what matters is that all the necessary pieces get built), but also about how things like the passing of data were accomplished by means of value objects, or how views were made to respond to changes in the application by binding to properties on the ModelLocator. This repetitive nature of building features is symptomatic of Cairngorm’s being an application framework. Recall that the purpose of an application framework is to provide a defined structure for building applications. It is this defined structure that allows multiple developers to work in parallel with reasonable confidence that the code they create will work together when assembled into a larger application. That being said, even with the structure provided by a Cairngorm framework, there are multiple methods for constructing features and differing opinions about them. The remaining sections of this chapter examine how you used Cairngorm to complete the features, using more or less the suggested methodology of the Cairngorm community (Adobe and the creators of Cairngorm). This methodology involves having one-to-one correspondences between events, commands, and when necessary delegates, and notifying views by using data binding. After this examination you will be briefly introduced to some other ideas of how Cairngorm can be used, so you can examine these methods and revise the code of the sample application to implement them.
218
Chapter 21: Reviewing Version One While I am examining these differing methods of using Cairngorm, it’s important to keep in mind that projects built with Cairngorm are often using the framework to facilitate team development. When this is the case it is important that the entire team be on the same page with regard to the methodology being used, since differing methodologies could lead to conflicting or redundant ways of accomplishing the same thing.
How Value Objects Have Been Used Value objects were used in a number of ways in the sample application. The first major use was for simplifying the transfer of related collections of data between classes and the backend (aka data transfer objects). By mirroring how data is stored on the backend in the properties of value objects, we made it simpler to pass data back and forth to the server (or in our case the local database). It’s simpler because you know how the data is being represented on both ends. The event, command, and delegate classes, as well as the FlexBlogDatabaseManager acting as the service simulator, all made use of value objects to pass data between classes and update the ModelLocator. Other common uses occurred in the views. Value objects were used to display the properties of a given value object through data binding. They were also used by various selection components such as lists to allow the user to select a value object that would then be passed to an event or update the model to change views.
How Events, Commands, and Delegates Have Been Used Events, commands, and delegates have been used in more or less the suggested way. Each event class had a corresponding command class and, when the backend needed to be accessed, a corresponding delegate. Event classes that needed to pass data did so by referencing value objects in public properties (with the exception of the KeyWordSearchEvent, for reasons we explained when you created the class). Because of this, those properties were available to the command classes via a reference to the dispatching event. Command classes would take in the data from the event and use it to either update the model (e.g., SetCurrentPostCommand) or pass the data along to a function of a delegate class. Delegate classes provided functions to access the backend. In most applications this would be a server call using one of the remoting classes such as the HTTPService. In the sample application, delegates instead called functions on the FlexBlogDatabaseManager. These functions passed back data to the calling command class using the result object of the ResultEvent or calling the fault function if something went wrong. In a typical application using something like HTTPService, this is all taken care of for you via the IResponder interface. Regardless, the basic idea remains the same: delegates serve as a proxy for interacting with the backend.
219
Chapter 21: Reviewing Version One The one-to-one correspondence between these classes has the benefit of making it easy to track down the logical flow of a given procedure. If you start with the view that dispatched a given event, you know exactly what other classes you need to look at to debug or update.
How Views Have Been Used Views have been broken down into discrete components that contain only the code necessary to make the view function. Additionally, you have kept the size of the main application and individual views small by composing larger views out of other components. This is primarily an organization principle that has the same benefits as breaking your code into classes. Namely, it is easier to locate the code related to a given view rather than to search through one large view file. Components can (and should if possible) also enable you to reuse views. For example, you were able to use the PostList view you created for both the recent posts feature and the search feature. In both these cases the fact that the user selected a post from the list meant that the post being displayed needed to be updated. Since the functionality of the PostList view takes care of this by dispatching the SetCurrentPostEvent, you did not need to recreate this functionality in the search view. Aside from binding to properties of the ModelLocator to update the contents displayed by views, you also bound to them to trigger setter functions that were used to allow views to react to events (such as clearing forms after submission and enabling buttons). Because of this mechanism, the ModelLocator does not need to have any knowledge of what function is being triggered on the view, or any knowledge of the view at all for that matter. Finally, views triggered actions by dispatching events. They had no direct knowledge of the code that was executed. Views were updated via data binding to properties on the ModelLocator. No other classes directly updated the views. They received notifications to update themselves by binding to properties on the ModelLocator. This means that you can easily change views to trigger different logic by changing the events they dispatch, or by updating the command classes that get triggered.
Looking Toward Version 2 As mentioned earlier in this chapter, despite the structure provided by Cairngorm, there are still different ways of accomplishing the same thing. One of the major complaints about Cairngorm is that it requires a lot of classes to be created. If you examine the src/com/FlexBlog folders you currently have, you’ll see these:
220
❑
Commands: 14 classes
❑
Controllers: 1 class
❑
Delegates: 9 classes
❑
Events: 14 classes
Chapter 21: Reviewing Version One ❑
Models: 1 class
❑
Value objects: 5 classes
❑
Views: 17 components
Ignoring the main application file and the database helper classes, this brings you to a total of 61 classes. Taking into account how simplified this application is, you can imagine that the full application would have many more. While the recommended use of events, commands, and delegates is to have a one-to-one correspondence, this is not absolutely necessary and you can reduce the number of classes by combining classes with similar purposes. Take events as an example. In the events that you created, the type parameter was removed from the constructor in favor of a constant defined in the event being directly passed to the parent constructor. Since there is a one-to-one correspondence between event and command classes, doing this enabled you to dispatch the event without specifying the type (since it would always be the same anyway). However, if you leave the type parameter in the constructor and simply define different constants for different types of events (which is more or less how the standard Flex events work), you can use the same event class to trigger multiple commands. For example, imagine you have a PostEvent class defined as follows: package com.FlexBlog.events { import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.control.CairngormEvent; public class PostEvent extends CairngormEvent { public static const LOAD_POSTS:String = “loadPosts”; public static const VIEW_POST:String = “viewPost”; public var post:PostVO; public function PostEvent(type:String, post:PostVO = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.post =post; } } }
You can then trigger different commands by doing the following in the FrontController: addCommand(PostEvent.LOAD_POSTS,LoadRecentPostCommand); addCommand(PostEvent.VIEW_POST, SetCurrentPostCommand);
This does, of course, have an impact on how your views need to dispatch the events, since they now need to specify the type, but that’s a relatively minor detail.
221
Chapter 21: Reviewing Version One Another place classes can be consolidated is in the delegate classes. Since delegates simply provide a function that can be called to access services, you can have multiple functions on the same delegate. For example, imagine you have a PostDelegate. You could simply consolidate the various functions in that single delegate, as in the following: package com.FlexBlog.delegates { import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.AsyncToken; import mx.rpc.IResponder; import mx.rpc.http.HTTPService; public class PostDelegate { private var responder:IResponder; public function PostDelegate(responder:IResponder) { this.responder = responder } public function loadPosts():void{ var service:HTTPService =ServiceLocator.getInstance().getHTTPService(‘loadPosts’); var token:AsyncToken = service.send(); token.addResponder(this.responder); } public function getByCategory(categoryId:int):void{ var service:HTTPService =ServiceLocator.getInstance().getHTTPService(‘getPostsByCategory’); var token:AsyncToken = service.send({categoryId:categoryId}); token.addResponder(this.responder); } } }
Another common issue faced by developers using Cairngorm is how to notify views of the results of procedure calls. The main issue is that you want your other classes to have no direct knowledge of the structure of views such as functions. In the sample applications you accomplished this by binding setter functions to properties on the ModelLocator. This of course means that you have to have properties on the ModelLocator that may do nothing but notify views of the status of procedures. If you have a fair number of procedures, this could potentially turn into a large number of properties. There are a number of solutions that don’t involve binding to properties on the ModelLocator. These include the use of ViewHelper and ViewLocator classes and the use of the mx.rpc.Responder class and mx.rpc.IResponder interface in your views. Each of these alternatives will be explored further in subsequent chapters.
Summary In this chapter you reviewed the methodologies that you have used thus far to create the sample application. You were then introduced to some alternative methods of building the same features. These alternative methods will be explored in more detail in subsequent chapters.
222
Combining Classes In this chapter you will examine ways to combine classes with related functionality to reduce the overall number of classes. In doing so, you will revise the features that you have created in the sample project.
Identifying Related Events and Delegates As mentioned in the previous chapter, there are at least two areas in which you can combine functionality to reduce the number of classes. These are your event and delegate classes.
Events Let’s start with event classes. The idea here is to find sets of actions that center on a particular object. Take for example the standard MouseEvent class. This class defines events such as MOUSE_DOWN, MOUSE_OUT, and MOUSE_OVER (and 14 other non-inherited events). These of course are all things that can happen when the user is using the mouse. So one possible way to combine Cairngorm events is to look for individual event classes that center on a particular type of object and then use constants to define these different types of events (i.e., make them more like standard Flex events). Currently you should have the following event classes: ❑
AddCommentEvent
❑
AddPostEvent
❑
ApplicationInitializeEvent
❑
CategorySearchEvent
❑
ChangeMainViewEvent
Chapter 22: Combining Classes ❑
ClearSearchResultsEvent
❑
KeyWordSearchEvent
❑
LoadCategoriesEvent
❑
LoadCommentsEvent
❑
LoadRecentPostsEvent
❑
LoginEvent
❑
LogoutEvent
❑
RegisterUserEvent
❑
SetCurrentPostEvent
Since the goal is to find sets of related behaviors, take a look at the list and ask yourself, “who” does this event? And by who, I am not necessarily referring to whatever triggered the event. That in many cases will be the user or the application. Perhaps a better way to phrase the question is “What object in the program does this behavior center on?” For example, let’s look at the idea of a post. In our event classes, posts are loaded (LoadRecentPostsEvent), added (AddPostEvent), and set to be the currently displayed post (SetCurrentPostEvent). These three classes could be represented as a single PostEvent class with the following constants: package com.FlexBlog.events { import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.control.CairngormEvent; public class PostEvent extends CairngormEvent { public static const ADD:String = “addPostEvent”; public static const LOAD_RECENT:String = “loadRecentPostsEvent”; public static const VIEW:String = “viewPostEvent”; public var post:PostVO; public function PostEvent(type:String, post:PostVO = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.post =post; } } }
Take note of how the constructor has changed as well as the call to the parent’s constructor. We have added the type parameter back in to the constructor and super is called using the type parameter, rather than defining a single constant and directly passing that (since that is no longer possible if you are defining multiple types). Also note that the post parameter has been defaulted to null. This allows classes and views that do not need to pass data to simply omit the parameter when dispatching the event.
224
Chapter 22: Combining Classes That being said, the PostVO is still useful in the same way that it was when we had individual event classes, in that it is an easy way to transfer data around. If we group related events around objects such as posts, the associated value objects still have relevance. The question simply becomes, “Do I need to pass one or not?” So now that you’ve seen an example with the PostEvent, take another look at the list of events and think about other potential actors. There is probably (there generally is) more than one possible organization. The principle guiding the organization that follows is to group actions around central objects so that the currently used value objects are still relevant. Categories currently have only a LoadCategoriesEvent event, but this is largely because the features you built required only the loading. There is a good chance that in a complete blogging application there will be an admin system that enables you to do things like add and edit categories, so it makes sense to represent these with a CategoryEvent class. The LoginEvent, LogoutEvent, and RegisterUserEvent events all center on the idea of a user. These could be consolidated into a UserEvent class. If you perform these consolidations the previous list becomes: ❑
LoadCommentsEvent
❑
AddCommentEvent
❑
PostEvent
❑
ApplicationInitializeEvent
❑
CategorySearchEvent
❑
ChangeMainViewEvent
❑
ClearSearchResultsEvent
❑
KeyWordSearchEvent
❑
CategoryEvent
❑
UserEvent
The classes you have consolidated thus far have been relatively straightforward in that they generally take in (if necessary) the same types of value objects if they need to pass data. The remaining event classes are not as straightforward in terms of what type of data might be expected for a given operation. For example, take the search events. Clearly they are related in that they center on the idea of searching, but keyword searches require only a string, while category searches take in a CategoryVO. Similarly, comments currently have LoadCommentsEvent and AddCommentEvent events, which can be consolidated into a single CommentEvent. However, the LoadCommentsEvent currently takes in a PostVO while the AddCommentEvent takes in a CommentVO. The idea here is that, when loading comments, the comments are associated with a particular post, and hence the postId of the PostVO is used to retrieve them.
225
Chapter 22: Combining Classes So the issue here is that different types of events will require different types of data and the resulting question is how to allow for multiple types (or in some cases none at all) to be passed by events. There are actually a couple of answers. If you recall the introductory chapter on events, you know that Cairngorm events have a property called data that can be used to transfer data to the command class. This, however, has an impact on how the event needs to be dispatched, as you need to set the property before dispatching the event. For example: var event:SomeEvent = new SomeEvent(); event.data = someObject; event.dispatch();
If some events require you to dispatch them this way, then the views (or rather those implementing them) need to know which events can be directly instantiated and dispatched and which ones require you to assign data to the data property before dispatching them. In other words, it destroys the consistency of how events can be used. You can restore the consistency by including a parameter in the constructor of your object that allows any type of data to be passed (as the Cairngorm data property does). For example: public function SomeEvent(type:String, data:* = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.data = data; }
This enables you to pass any type of data and still work with event classes consistently. So the previous example, in which you had to set the data property, could now be rewritten as follows: new SomeEvent(SomeEvent.SOME_TYPE, someOject).dispatch();
Now that you have the issue of passing multiple types of data taken care of, the question becomes how you respond when multiple types of data can be passed by an event. When multiple types of data can be passed, the command classes paired with the particular type event become responsible for casting the data to the expected type. You have done similar things when working with Cairngorm events in general. For example, in the execute function of your command classes you often did something like the following: public function execute(event:CairngormEvent):void { var evt:SomeEvent = event as SomeEvent; }
In addition to this, you would now be doing something like this: var data:CategoryVO = evt.data as CategoryVO;
So it is possible to pass different types of data in events and have command classes respond accordingly. However, as with most things, there is a trade-off.
226
Chapter 22: Combining Classes In the old system that made the data objects require parameters, the compiler would yell at you if the parameter was not passed or was the wrong type. Since these parameters are now optional, and in some cases can be of any type, it becomes the responsibility of the dispatcher to provide the correct type of data when necessary. So with those limitations in mind, the final consolidated list of events becomes: ❑
CommentEvent
❑
PostEvent
❑
ApplicationInitializeEvent
❑
SearchEvent
❑
ChangeMainViewEvent
❑
CategoryEvent
❑
UserEvent
You started with fourteen event classes and now have seven. We are leaving the ChangeMainViewEvent and ApplicationInitializeEvent since they perform relatively specific tasks, and the revisions being done to the other features will sufficiently demonstrate how you can revise the code using consolidated events and delegates.
Delegates You can accomplish a similar consolidation by combining related functions on delegates. Currently you should have the following delegate classes: ❑
AddCommentDelegate
❑
AddPostDelegate
❑
CategorySearchDelegate
❑
KeyWordSearchDelegate
❑
LoadCategoryDelegate
❑
LoadCommentsDelegate
❑
LoadRecentPostsDelegate
❑
LoginDelegate
❑
RegisterUserDelegate
227
Chapter 22: Combining Classes You can use the revisions you made to your event classes as a guideline for consolidating your delegate classes: ❑
CommentDelegate
❑
PostDelegate
❑
SearchDelegate
❑
CategoryDelegate
❑
UserDelegate
This reduces the number of delegate classes from nine to five. The consolidation process is simply a matter of moving the functions from the existing delegate classes into the new delegate classes. You will do this as you revise the features.
What About Commands? You may be asking yourself, “Why not also consolidate command classes?” Commands get tricky to consolidate because doing so would require logic inside the command to determine what event type was being handled. This would mean conditional statements in the execute, result, and fault functions. This would get messy quickly and somewhat (if not entirely) defeats the point of separating the logic into distinct commands in the first place. Thus, it is still better to have a single command class for a given event type.
Revising Registration Registration is currently accomplished with the following classes: ❑
RegisterUserEvent
❑
RegisterUserDelegate
❑
RegisterUserCommand
The RegisterUserEvent will become a type of the new UserEvent class. The register function of the RegisterUserDelegate will move to the UserDelegate class. The execute function of the RegisterUserCommand will be updated to use the new UserEvent and UserDelegate classes.
228
Chapter 22: Combining Classes Start by creating the user event class. In the com.FlexBlog.events package create a new event class named UserEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.UserVO; import com.adobe.cairngorm.control.CairngormEvent; public class UserEvent extends CairngormEvent { public static const REGISTER:String = ‘registerUserEvent’; public var user:UserVO; public function UserEvent(type:String, user:UserVO = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.user = user; } } }
Now create the new user delegate class. In the com.FlexBlog.delegates package create a new delegate class named UserDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.UserVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class UserDelegate { private var responder:IResponder; public function UserDelegate(responder:IResponder) { this.responder =responder; } public function register(user:UserVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var userAdded:int = dbManager.regsiterUser(user); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:userAdded}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
229
Chapter 22: Combining Classes Note that the register function has been moved over exactly as it existed in the RegisterUserDelegate. Update the execute function of the RegisterUserCommand to cast the event as a UserEvent and use the new UserDelegate class, as in the following: public function execute(event:CairngormEvent):void { this.model.registrationStatus = ‘’; var evt:UserEvent = event as UserEvent; var delegate:UserDelegate = new UserDelegate(this); delegate.register(evt.user); }
Make sure Flex updated the import statements for you. Now update the FlexBlogController. Edit the addCommand function call that registers the RegisterUserCommand, as follows: addCommand(UserEvent.REGISTER, RegisterUserCommand);
Next, update the doRegistration function in the RegistrationForm view to dispatch the new event. Replace new RegisterUserEvent(user).dispatch();
with new UserEvent(UserEvent.REGISTER, user).dispatch();
Make sure Flex imports the new event class and you can remove the import statement for the old RegisterUserEvent (views do not seem to update the import references as consistently as classes do, or at least they did not when I was building the sample application). Now debug the application and quickly register a new account and confirm that you receive the account created message, as in Figure 22-1.
Figure 22-1
230
Chapter 22: Combining Classes
Revising Login Login and logout are currently accomplished with the following classes: ❑
LoginEvent
❑
LogoutEvent
❑
LoginDelegate
❑
LoginCommand
❑
LogoutCommand
The LoginEvent and LogoutEvent will be replaced with types on the UserEvent class. The login function of the LoginDelegate will be moved to the UserDelegate. The LoginCommand will be updated to cast the event as a UserEvent and use the new UserDelegate. The LogoutCommand does not make use of a delegate, nor does it reference any data passed in by the event class, so it will remain as is. Start by updating the UserEvent class by adding the following two constants: public static const LOGIN:String = ‘loginEvent’; public static const LOGOUT:String = ‘logoutEvent’;
Now move the login function from the LoginDelegate class to the UserDelegate class. It should now look as follows: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.UserVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class UserDelegate { private var responder:IResponder; public function UserDelegate(responder:IResponder) { this.responder =responder; } public function register(user:UserVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var userAdded:int = dbManager.registerUser(user); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:userAdded}) this.responder.result(event)
231
Chapter 22: Combining Classes }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } public function login(user:UserVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var isUser:UserVO = dbManager.login(user); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{user:isUser}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
Next, update the execute function of the LoginCommand class to cast the event as a user event and use the new UserDelegate class, as in the following: public function execute(event:CairngormEvent):void { var evt:UserEvent = event as UserEvent; var delegate:UserDelegate = new UserDelegate(this); delegate.login(evt.user); }
Make sure Flex updated the import statements for you. Now update the FlexBlogController. Edit the addCommand calls that register the LoginCommand and LogoutCommand commands. Replace addCommand(LoginEvent.LOGIN,LoginCommand); addCommand(LogoutEvent.LOGOUT,LogoutCommand);
with addCommand(UserEvent.LOGIN,LoginCommand); addCommand(UserEvent.LOGOUT,LogoutCommand);
Next, update the doLogin function of the Login view to dispatch the new UserEvent. Replace new LoginEvent(user).dispatch();
with new UserEvent(UserEvent.LOGIN,user).dispatch();
232
Chapter 22: Combining Classes Then change the click event of the Logout button event to dispatch a UserEvent. Replace new LogoutEvent().dispatch()
with new UserEvent(UserEvent.LOGOUT).dispatch();
Debug the application and log in with the poe account (username = poe, password = password). You should see the welcome message (Figure 22-2), which is at the upper right of the screen.
Figure 22-2 Click the Logout button and confirm that the login form is again displayed.
Revising Adding a Post Adding a post is currently accomplished with the following classes: ❑
AddPostEvent
❑
AddPostDelegate
❑
AddPostCommand
❑
LoadCategoriesEvent
❑
LoadCategoriesCommand
❑
LoadCategoriesDelegate
The AddPostEvent will become a type of the new PostEvent class. The addPost function of the AddPostDelegate will move to the new PostDelegate class. The AddPostCommand will be updated to cast the event as a PostEvent and use the new PostDelegate. The LoadCategoriesEvent will become a type of the CategoryEvent class. The loadCategories function of the LoadCategoriesDelegate will be moved to the CategoryDelegate class. The execute function of the LoadCategoriesCommand will be updated to use the CategoryDelegate.
233
Chapter 22: Combining Classes Start with the new event class for posts. In the com.FlexBlog.events package create a new event class named PostEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.PostVO; import com.adobe.cairngorm.control.CairngormEvent; public class PostEvent extends CairngormEvent { public static const ADD:String = ‘addPostEvent’; public var post:PostVO; public function PostEvent(type:String, post:PostVO = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.post =post; } } }
Now create the new post delegate class. In the com.FlexBlog.delegates package create a new delegate class named PostDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.PostVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class PostDelegate { private var responder:IResponder public function PostDelegate(responder:IResponder) { this.responder =responder; } public function addPost(post:PostVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var added:int = dbManager.addPost(post); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:added}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
234
Chapter 22: Combining Classes Now create the new category event class. In the com.FlexBlog.events package create a new event class named CategoryEvent. Edit the class to match the following: package com.FlexBlog.events { import com.FlexBlog.valueobjects.CategoryVO; import com.adobe.cairngorm.control.CairngormEvent; public class CategoryEvent extends CairngormEvent { public static const LOAD:String = ‘loadCategoriesEvent’; public var category:CategoryVO; public function CategoryEvent(type:String, category:CategoryVO = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.category =category; } } }
Next, create the category delegate class. In the com.FlexBlog.delegates package create a new delegate class named CategoryDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class CategoryDelegate { private var responder:IResponder; public function CategoryDelegate(responder:IResponder) { this.responder = responder; } public function loadCategories():void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var categories:Array = dbManager.getCategories(); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{categories:categories}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
235
Chapter 22: Combining Classes Now update the FrontController. Edit the addCommand calls that register the AddPostCommand and the LoadCategoriesCommand. Replace addCommand(LoadCategoriesEvent.LOAD, LoadCategoriesCommand); addCommand(AddPostEvent.ADD_POST,AddPostCommand);
with addCommand(PostEvent.ADD,AddPostCommand); addCommand(CategoryEvent.LOAD, LoadCategoriesCommand);
Next, update the execute function of the AddPostCommand to use the new PostEvent and PostDelegate classes: public function execute(event:CairngormEvent):void { this.model.postSubmissionStatus =’’ var evt:PostEvent = event as PostEvent var delegate:PostDelegate = new PostDelegate(this); delegate.addPost(evt.post); }
Then change the execute function of the ApplicationInitializeCommand to use the new CategoryEvent: public function execute(event:CairngormEvent):void { new CategoryEvent(CategoryEvent.LOAD).dispatch(); new LoadRecentPostsEvent().dispatch(); }
Make sure Flex updated the import statements for you. Now update the submitPost function of the PostEditor view to dispatch the new PostEvent. Replace new AddPostEvent(post).dispatch();
with new PostEvent(PostEvent.ADD,post).dispatch();
You can remove the import statement for the old AddPostEvent and make sure that Flex imported the PostEvent for you (again, views do not always update as well as classes). Debug the application and login with the poe account (username = poe, password = password). Switch to the write view and confirm that the categories are showing up in the combo box. Then quickly add a post. Confirm that you see the “Post added” message, at the left side of Figure 22-3.
236
Chapter 22: Combining Classes
Figure 22-3
Revising Loading Posts The loading and displaying of posts is currently accomplished with the following classes. ❑
LoadRecentPostsEvent
❑
LoadRecentPostsDelegate
❑
LoadRecentPostsCommand
❑
SetCurrentPostEvent
❑
SetCurrentPostCommand
The LoadRecentPostsEvent and SetCurrentPostEvent will become types on the PostEvent class. The loadRecentPosts function of the LoadRecentPostsDelegate will be moved to the PostDelegate. The LoadRecentPostsCommand will be updated to use the PostDelegate. The SetCurrentPostCommand will be updated to cast the event as a PostEvent. Start by adding the following constants to the PostEvent class: public static const LOAD:String = ‘loadPostsEvent’;
Move the loadRecentPosts function from the LoadRecentPostsDelegate to the PostDelegate. The PostDelegate should now look like this: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.PostVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class PostDelegate { private var responder:IResponder public function PostDelegate(responder:IResponder)
237
Chapter 22: Combining Classes { this.responder =responder; } public function addPost(post:PostVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var added:int = dbManager.addPost(post); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:added}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } public function loadRecentPosts():void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.getRecentPosts(); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{posts:posts}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
Update the execute function of the LoadRecentPostsCommand to use the PostDelegate classes: public function execute(event:CairngormEvent):void { var delegate:PostDelegate = new PostDelegate(this); delegate.loadRecentPosts(); }
Then update the result function to dispatch the new event for viewing posts: public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.posts){ this.model.recentPosts.source = evt.result.posts; if(!this.model.currentPost){ new PostEvent( PostEvent.VIEW,PostVO(this.model.recentPosts.getItemAt(0))).dispatch(); } } }
238
Chapter 22: Combining Classes Update the execute function of the SetCurrentPostCommand to cast the event to PostEvent: public function execute(event:CairngormEvent):void { var evt:PostEvent = event as PostEvent; this.model.currentPost = evt.post; new LoadCommentsEvent(this.model.currentPost).dispatch(); }
Update the FrontController. Edit the addCommand calls that register the LoadRecentPostsCommand and SetCurrentPostCommand. Replace addCommand(LoadRecentPostsEvent.LOAD, LoadRecentPostsCommand); addCommand(SetCurrentPostEvent.SET,SetCurrentPostCommand);
with addCommand(PostEvent.LOAD, LoadRecentPostsCommand); addCommand(PostEvent.VIEW, SetCurrentPostCommand); addCommand(LoadRecentPostsEvent.LOAD, LoadRecentPostsCommand); addCommand(SetCurrentPostEvent.SET,SetCurrentPostCommand); ]
Next, update the execute function of the ApplicationInitializeCommand to dispatch the new PostEvent: public function execute(event:CairngormEvent):void { new LoadCategoriesEvent().dispatch(); new PostEvent(PostEvent.LOAD).dispatch(); }
Then update the onPostSelected function of the PostList view to dispatch the new PostEvent. Replace new SetCurrentPostEvent(PostVO(this.selectedItem)).dispatch();
with new PostEvent(PostEvent.VIEW,PostVO(this.selectedItem)).dispatch();
Debug the application and confirm that posts are still being loaded and displayed. Click on one of the recent posts’ titles and confirm that the displayed post changes. Click one of the categories to bring up the search view and click a post in the search results to confirm that the displayed post changes from there as well.
239
Chapter 22: Combining Classes
Revising Commenting The commenting system is currently accomplished with the following classes: ❑
AddCommentEvent
❑
AddCommentDelegate
❑
AddCommentCommand
❑
LoadCommentsEvent
❑
LoadCommentsDelegate
❑
LoadCommentsCommand
The AddCommentEvent and LoadCommentsEvent will become properties of the CommentEvent class. As mentioned earlier, these two types of events currently expect different value objects, so the type of the property holding the data to be passed will have to be typed as Object to accommodate different types of value objects. The addComment function of the AddCommentDelegate and the loadComments function of the LoadCommentsDelegate will be moved to the CommentDelegate class. The AddCommentCommand will be updated to cast the event as a CommentEvent and both the AddCommentCommand and the LoadCommentsCommand will be updated to use the CommentDelegate class. Start by creating the new comment event. In the com.FlexBlog.events package create a new event class named CommentEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class CommentEvent extends CairngormEvent { public static const ADD:String = ‘addCommentEvent’; public static const LOAD:String = ‘loadCommentsEvent’; public function CommentEvent(type:String, data:Object = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.data = data; } } }
240
Chapter 22: Combining Classes Here is the first case in which the constructor is being designed to take in multiple types of objects. The data parameter has been typed as Object, since in this case the data will be one of two value objects. This object is then stored in the data property inherited from the CairngormEvent class. Next, create the comment delegate class. In the com.FlexBlog.delegates package create a new delegate class named CommentDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.CommentVO; import com.FlexBlog.valueobjects.PostVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class CommentDelegate { private var responder:IResponder; public function CommentDelegate(responder:IResponder) { this.responder = responder; } public function addComment(comment:CommentVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var added:int = dbManager.addComment(comment); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{added:added}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } public function loadComments(post:PostVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var comments:Array = dbManager.getComments(post); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{comments:comments}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
241
Chapter 22: Combining Classes Now update the execute and result functions of the AddCommentCommand class: Update the execute function to use the CommentEvent and CommentDelegate classes, as in the following: public function execute(event:CairngormEvent):void { this.model.commentSubmissionStatus =’’ var evt:CommentEvent = event as CommentEvent; var delegate:CommentDelegate = new CommentDelegate(this); delegate.addComment(CommentVO(evt.data)); }
Note that the data property from the event is now being used and is being cast to a CommentVO. Update the result function to load the comments with the CommentEvent class, as in the following: public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.added == 1){ this.model.commentSubmissionStatus =’success’ new CommentEvent(CommentEvent.LOAD, this.model.currentPost).dispatch(); } }
Now update the execute function of the LoadCommentsCommand to use the CommentDelegate, as in the following: public function execute(event:CairngormEvent):void { var evt:CommentEvent = event as CommentEvent; var delegate: CommentDelegate = new CommentDelegate(this); delegate.loadComments(PostVO(evt.data)); }
Again, the data property of the event is now being used, but this time it is being cast to a PostVO. Next, update the execute function of the SetCurrentPostCommand to load comments using the CommentEvent, as in the following: public function execute(event:CairngormEvent):void { var evt:PostEvent = event as PostEvent; this.model.currentPost = evt.post; new CommentEvent(CommentEvent.LOAD,this.model.currentPost).dispatch(); }
Make sure Flex updated the import statements for you in all your command classes.
242
Chapter 22: Combining Classes Now update the FlexBlogController. Edit the addCommand calls that register the AddCommentCommand and LoadCommentsCommand. Replace addCommand(LoadCommentsEvent.LOAD,LoadCommentsCommand); addCommand(AddCommentEvent.ADD,AddCommentCommand);
with addCommand(CommentEvent.LOAD,LoadCommentsCommand); addCommand(CommentEvent.ADD,AddCommentCommand);
Finally, update the submitComment function of the CommentForm view to dispatch the new CommentEvent. Replace new AddCommentEvent(comment).dispatch();
with new CommentEvent( CommentEvent.ADD,comment).dispatch();
Make sure Flex updated the import statements for you. You may have to manually remove the old AddCommentEvent import statement. Debug the application and log in with the poe account (user name = poe, password = password). Add a comment to the current displayed post and confirm that it shows up, as in Figure 22-4.
Figure 22-4
243
Chapter 22: Combining Classes Select a different post from the recent posts list and then reselect the post you commented on and confirm that the comment is again displayed. Close the application and relaunch it in debug mode to confirm that the comment you added is loaded when the application loads.
Revising Searching Currently the search feature is accomplished with the following classes: ❑
CategorySearchEvent
❑
KeyWordSearchEvent
❑
ClearSearchResultsEvent
❑
CategorySearchDelegate
❑
KeyWordSearchDelegate
❑
CategorySearchCommand
❑
ClearSearchResultsCommand
❑
KeyWordSearchCommand
The CategorySearchEvent, KeyWordSearchEvent, and ClearSearchResultsEvent classes will become types of the SearchEvent class. Again, since these events currently expect different types of data to be passed, you will have to construct the event to allow for multiple data types. The CategorySearchDelegate and the KeyWordSearchDelegate will be consolidated into a SearchDelegate class. However, in this case the functions that exist on both delegates are named search, and they will have to be renamed when they are moved to the new delegate. The execute functions of the CategorySearchCommand and KeyWordSearchCommand commands will have to be updated to cast the data passed by the SearchEvent and to use the new SearchDelegate. The ClearSearchResultsCommand does not use any data passed from the event, nor does it use a delegate, so this class can remain as it is. Start by creating the new search event. In the com.FlexBlog.events package create a new event class named SearchEvent. Edit the class to match the following: package com.FlexBlog.events { import com.adobe.cairngorm.control.CairngormEvent; public class SearchEvent extends CairngormEvent { public static const KEYWORD_SEARCH:String = ‘keywordSearchEvent’; public static const CATEGORY_SEARCH:String = ‘categorySearchEvent’; public static const CLEAR_RESULTS:String = ‘clearResultsEvent’; public function SearchEvent(type:String, data:Object = null,
244
Chapter 22: Combining Classes bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.data = data; } } }
Note that here again you are using the inherited data property with a constructor parameter typed to Object. Next, create the search delegate class. In the com.FlexBlog.delegates package create a new delegate class named SearchDelegate. Edit the class to match the following: package com.FlexBlog.delegates { import com.FlexBlog.valueobjects.CategoryVO; import flash.errors.SQLError; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import sql.FlexBlogDatabaseManager; public class SearchDelegate { private var responder:IResponder public function SearchDelegate(responder:IResponder) { this.responder = responder; } public function categorySearch(category:CategoryVO):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.getPostsByCategory(category); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{posts:posts}) this.responder.result(event) }catch(error:SQLError){ var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } public function keywordSearch(keyWords:String):void{ var dbManager:FlexBlogDatabaseManager = FlexBlogDatabaseManager.getInstance(); try{ var posts:Array = dbManager.keyWordSearch(keyWords); var event:ResultEvent = new ResultEvent(ResultEvent.RESULT,false,true,{posts:posts}) this.responder.result(event) }catch(error:SQLError){
245
Chapter 22: Combining Classes var faultEvent:FaultEvent = new FaultEvent(FaultEvent.FAULT); this.responder.fault(faultEvent); } } } }
Note that the function names are now categorySearch and keywordSearch. Now update the execute function of the KeyWordSearchCommand to use the new SearchEvent and SearchDelegate classes: public function execute(event:CairngormEvent):void { this.model.searchTitle = “Searching ...” var evt:SearchEvent = event as SearchEvent; this.keyWords = String (evt.data); var delegate:SearchDelegate = new SearchDelegate(this); delegate.keywordSearch(this.keyWords); }
Note that the data from the event is being cast to a string and that the function being called on the delegate is now keywordSearch. Next update the execute function of the CategorySearchCommand to use the new SearchEvent and SearchDelegate classes: public function execute(event:CairngormEvent):void { var evt:SearchEvent = event as SearchEvent; this.category = CategoryVO(evt.data); var delegate:SearchDelegate = new SearchDelegate(this); delegate.categorySearch(this.category); }
This time the data property of the event is cast to a CategoryVO and the delegate function has been changed to categorySearch. Make sure Flex updated the import statements for you in your command classes. Now update the FrontController. Change the addCommand calls that register the CategorySearchCommand, KeyWordSearchCommand, and ClearSearchResultsCommand. Replace addCommand(KeyWordSearchEvent.KEYWORD_SEARCH, KeyWordSearchCommand) addCommand(CategorySearchEvent.CATEGORY_SEARCH, CategorySearchCommand); addCommand(ClearSearchResultsEvent.CLEAR_RESULTS, ClearSearchResultsCommand);
with addCommand(SearchEvent.KEYWORD_SEARCH, KeyWordSearchCommand) addCommand(SearchEvent.CATEGORY_SEARCH, CategorySearchCommand); addCommand(SearchEvent.CLEAR_RESULTS, ClearSearchResultsCommand);
246
Chapter 22: Combining Classes Next, update the click event of the searchButton in the SearchBoxView to trigger a SearchEvent:
Then update the itemClick event of the List in the CategorySearchView to dispatch a SearchEvent:
Finally, update the click event of the clearSearchResultsButton in the SearchResultsView to dispatch a SearchEvent:
Make sure Flex updated the import statements for you; you may have to manually remove the old event class import statements. Debug the application and do a keyword search for “china.” Your screen should look like Figure 22-5.
Figure 22-5
247
Chapter 22: Combining Classes Now click the Politics category to perform a category search. Your screen should look like Figure 22-6.
Figure 22-6 Finally, click the Clear button in the search view and confirm that the search view hides itself.
Summary In this chapter you consolidated event and command classes with related functionalities. You then revised the existing features of the sample application to use these new classes. Let’s take a moment to examine the basic process that you used to do this. Discrete event classes became type constants in a common event class. They were generally grouped together by the object they centered on (e.g., posts) and the value objects they used to pass data (which on some level are related to the central object). In some cases the same event class would need to pass multiple types of data (e.g., comments and search). If this was the case, the property used to transfer data was typed to Object and stored in the inherited data property of the CairngormEvent class. It then became the responsibility of the command class to cast the data property to the appropriate type. You consolidated delegates with similar purposes by moving related functions into a single delegate class. You can see, from this exercise of updating the existing features, some of the flexibility afforded by using an application framework. The changes you had to make were generally the same across features (update how events were dispatched, update commands, and so on) and the locations in which you made the changes were predictable. On the other hand, you can also see that you probably want to decide how you are going to structure your events and delegates before you start to code the application, since the changes you had to make when switching systems were not trivial either. If you are working in a team environment, more than likely these decisions will be made at the team level, since you need to make sure that everyone is coding in a compatible manner. You can take a look at the consolidated version of the application in its entirety (i.e., with the old classes removed) in the /Resources/code/ch22 folder of the source code for this book.
248
Calling Methods on Views In this chapter you’ll examine methods of notifying views of the success or failure of a logical operation without having to bind to properties on the ModelLocator.
The Problem In earlier versions, the registration, adding of posts, and commenting features all required that certain things happen after certain procedures were performed. For example, the forms had to be cleared and the buttons enabled if an operation was successful. In other words, your views needed to be able to react to the status of procedure calls beyond simply displaying data held in the ModelLocator. In the current version of the project you arranged this by binding to variables on the ModelLocator that indicated the success or failure of the request. However, as with most things, there is more than one way to notify views of the status of a procedure call. That being said, since you are using a framework that is based on the MVC paradigm, there are a few requirements you need to meet when doing so. When you use the MVC paradigm, the control and model layers of the framework should not have to know anything about the implementation of the views. Requiring knowledge of a view’s implementation increases the coupling between objects and gets complicated when multiple views need to react to the same procedure in different ways. The binding of setter functions to properties on the model satisfies the requirement that the model and the control layer have no knowledge of what functions are being called on the view, or even what views are listening for the changes. The model has no idea who is binding to the status properties, and the command classes simply update the properties on the model to indirectly update views. Data binding works well enough, but requires you to have properties on the model for each possible operation, which your command classes then have to update. As the number of features
Chapter 23: Calling Methods on Views grows, this can turn into a substantial number of properties on the model that have no other purpose than to notify views of the success and failure of operations. In the rest of this chapter you’ll explore alternative methods of notifying views of the results of a procedure call and you’ll revise the registration feature of the sample application to use one of these methods.
ViewHelper and ViewLocator The problem of notifying views without having to bind to properties on the ModelLocator was one the creators of Cairngorm took into account. In the com.adobe.cairngorm.view package you will find two classes: the ViewHelper and the ViewLocator (both are currently deprecated). The documentation for the ViewHelper class describes it in this way: “Used to isolate command classes from the implementation details of a view.” Cairngorm best practices specify that command classes should interact with the view using the model, but in some instances command classes may need to both interrogate and update the view directly. Before performing any business logic, the command class may need to fetch values that have been set on the view. Following completion of any business logic, the final task may be for a command class to update the view (user interface) with any results returned, or perhaps to switch the view entirely (to a different screen). By encapsulating all the logic necessary for interrogating and updating a particular view into a single helper class, we remove the need for the command classes to have any knowledge about the implementation of the view. The ViewHelper class decouples our presentation from the control of the application. A ViewHelper belongs to a particular view in the application; when a ViewHelper is created, its id is used to register a particular view component (such as a particular tab in a TabNavigator, or a particular screen in a ViewStack). The developer then uses the ViewLocator to locate the particular ViewHelper for interrogating or updating a particular view. In http://cairngormdocs.org, the ViewLocator class is described as follows (with a redundant description of MVC requirements removed): The ViewLocator is a singleton class, that is used to retrieve ViewHelper classes that can manipulate (get/ set/switch) the user interface of a Cairngorm RIA. The ViewLocator class is used to allow commands to instantly retrieve the appropriate ViewHelper. A command need only know the canonical name of a ViewHelper and the ViewLocator will return an instance of the appropriate ViewHelper class. In this way, command classes can manipulate the View irrespective of its implementation. (From http://cairngormdocs.org/docs/cairngorm_2_2_1/ index.html)
250
Chapter 23: Calling Methods on Views You can also read in the documentation that both of these classes have been deprecated as of Cairngorm 2.1. In fact, if you do a search for “Cairngorm ViewLocator ViewHelper,” rather than finding examples of how to use these classes, you’ll find a number of blog posts stating that their use is bad practice and should be avoided. The underlying theme of the argument against their use is that they require you to look up a view and then call a method on that view. This means that despite these helper classes, the control layer now has to have some knowledge of what view to look up and what method to call on that view. This increases the dependencies between classes. Thus, the claim about decoupling made in the documentation’s descriptions of the classes is a bit misleading, which may be one of the reasons they are now deprecated. So why bring them up at all? First, their existence does indicate that even the creators of Cairngorm recognized that there are situations in which you may want to trigger logic on views without having to bind to the ModelLocator. Second, simply for the sake of completeness: if you happen to be working on a project that was built with an older version of Cairngorm you may run across them and should at least know what you’re looking at and why they may have been used. That said, we’re now going to pass over them in favor of another method of notifying views that does not require the level of coupling that the ViewLocator and ViewHelper classes do.
The iResponder Inter face and the Responder Class Thomas Burleson of Universal Mind wrote an article titled “Cairngorm Secret Tip #3: Responders for View Notifications” (http://www.gridlinked.info/cairngorm-secret-tip-3-respondersfor-view-notifications/), in which he puts forth a methodology for notifying views of the results of logical processes that does not require passing a reference to the view. This methodology hinges on using the IResponder interface and Responder class of the Flex framework. Take note that the code examples in the article make use of specific Universal Mind classes (http:// code.google.com/p/flexcairngorm/) that are not part of the standard Cairngorm framework. So you will be implementing the basic ideas expressed in the article, but you will not be implementing them exactly as described. You’ve already used the IResponder interface in your command classes. This interface enables you to specify functions that will respond to the result and fault events of asynchronous calls such as HTTPServices and the like. In this case you’re going to have your views act as responders. However, you’re not going to set this up in quite the same way as your command classes. As pointed out in Burleson’s article, having views act as IResponders raises a design question in regard to your views. Namely, if your views are going to act as responders, does this now mean that your views
251
Chapter 23: Calling Methods on Views need to implement the IResponder interface? Burleson points out a couple of problems with this approach. Take the following example from the article:
In the preceding, the required IResponder result and fault functions are defined and in the triggerServiceCall function the view passes itself as an IResponder to the SampleEvent in the following line: var callbacks : IResponder = this as IResponder;
This of course assumes that the event is set up to take in an IResponder as a parameter and that your command class is set up to handle responders, which will be requirements for using this method of notifying views. As Burleson points out, implementing the IResponder interface in your views presents a number of problems. What if the view dispatched multiple events that needed to be responded to? In the current design you have only two functions that can handle callbacks. If you need to respond to multiple events, you have to include logic in the result and fault functions to determine what type of event is being responded to and act accordingly. This kind of code quickly becomes overly complex and a nightmare to maintain. In fact, Thomas politely suggests “thoroughly slapping the developer” if you see such code. So how does one avoid such a slapping? The mx.rpc package also contains a class called Responder, a description of which can be found at http://livedocs.adobe.com/flex/3/langref/mx/rpc/Responder.html.
252
Chapter 23: Calling Methods on Views The Responder class provides a default implementation of the responder interface. The constructor of this class is passed references to the functions that will handle the result and fault events of the service call, as in the following: var responder : IResponder = new Responder(resultHandlerFunction ,faultHandlerFunction);
By using the Responder class, you can have multiple functions in the view that can respond to various service calls and simply pass in the functions that are responsible for responding to the event being invoked, as in the following:
253
Chapter 23: Calling Methods on Views Now, in the function that triggers the service calls, you are specifying the functions that will respond to each service call in the following lines: var responder : IResponder = new Responder(serviceOneResult, serviceOneFault); var responder : IResponder = new Responder(serviceTwoResult, serviceTwoFault);
This avoids the view’s having to directly implement the IResponder interface and needing conditional logic in the result and fault functions to determine what event is being responded to. Additionally this system is much more flexible, enabling you to respond to as many events as necessary simply by adding new handler functions and passing them to instances of the Responder class. In order for you to use this method of notifying views, your event and command classes are going to have to take in an instance of the Responder class. However, it may be the case that not all the views that trigger a particular event need to be notified of the results. To allow such views to trigger the event without having to create an empty responder instance, you can define the responder parameter as optional, as in the following: public function SomeEvent(responder:IResponder= null, bubbles:Boolean=false, cancelable:Boolean=false)
Since the parameter is optional, you can have multiple views that trigger the same event, and only those that need to be informed of the results have to implement the necessary logic to respond to them. Then in the command class you can simply determine if the responder has been defined and pass it the data returned by any service calls: public function result(data:Object):void{ //update model if (this.responder){ this.responder.result(data); } }
Revising Registration To see how this Responder methodology works, you are going to revise the registration feature. First revise the UserEvent class to take in an optional IResponder parameter to be held in a new responder property. Add the following variable to the UserEvent class: public var responder:IResponder;
254
Chapter 23: Calling Methods on Views Then update the constructor to take in an optional responder parameter and assign it to the responder property: public function UserEvent(type:String, user:UserVO = null, responder:IResponder = null, bubbles:Boolean=false, cancelable:Boolean=false) { super(type, bubbles, cancelable); this.user = user; this.responder = responder; }
Now update the RegisterUserCommand. Start by adding the following property: private var responder:IResponder;
Next, update the execute function to assign the responder passed by the event to the responder property. Additionally, remove the code that updates the registrationStatus property of the ModelLocator, as this will no longer be needed: public function execute(event:CairngormEvent):void { var evt:UserEvent = event as UserEvent; this.responder = evt.responder; var delegate:UserDelegate = new UserDelegate(this); delegate.register(evt.user); }
Now update the result function of the RegisterUserCommand to trigger the result function of the responder passing along the event object, if one has been defined, and remove the code updating the registrationStatus property of the ModelLocator: public function result(data:Object):void { var event:ResultEvent = data as ResultEvent; var notification:UserNotificationVO = new UserNotificationVO(); if (event.result.added ==1){ notification.message = ‘Your account has been created.’ }else if(event.result.added ==-1){ notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘An account with that user name and password already exists.’ }else{ notification.type = UserNotificationVO.ERROR_MESSAGE; notification.message = ‘Account could not be created at this time. Please try again later.’ } this.model.userNotification = notification; if(this.responder){ this.responder.result(data); } }
255
Chapter 23: Calling Methods on Views Do the same for the fault function, but call it the responder: public function fault(info:Object):void { var notification:UserNotificationVO = new UserNotificationVO(); notification.message = ‘There was an error processing your registration, please try again.’ notification.type = UserNotificationVO.ERROR_MESSAGE; this.model.userNotification = notification; if (this.responder){ this.responder.fault(info); } }
Now edit the HBox with the id RegisterView to remove the binding on the registrationComplete function of the RegistrationFrom component:
Since the binding is now gone and you have removed the references to it from the command class, you can remove the registrationStatus property from the ModelLocator. Next, add the following import to the RegistrationForm component: import mx.rpc.Responder;
Now update the doRegistration function to dispatch the UserEvent, as follows: var responder:mx.rpc.Responder = new mx.rpc.Responder(this.onRegistrationComplete,this.onRegistrationFault) ]
Replace the following line new UserEvent(UserEvent.REGISTER, user).dispatch();
with new UserEvent(UserEvent.REGISTER, user, responder).dispatch();
The full package name is being used in the responder declaration because there is another Responder class found in the flash.net package. Despite the import statement, Flex may complain either that the class could not be found or that you were trying to use the wrong type of responder. This may not occur for you, but if it does, using the full package declaration will remove the error.
256
Chapter 23: Calling Methods on Views Next, replace the current registrationComplete setter function with the following: private function onRegistrationComplete(event:ResultEvent):void{ if(event.result.added ==1){ this.clearForm(); } this.registerButton.enabled = true; this.cancelButton.enabled = true; }
Make sure that Flex has added the import statement for the ResultEvent for you. Even though you are not going to do anything with it, the Responder class constructor requires a function be defined for the fault handler. You could pass null, but this would cause issues if the fault function of the responder is called. Add the following function: private function onRegistrationFault(event:FaultEvent):void{}
Make sure that Flex has added the import statement for the FaultEvent for you. Debug the application and register a user. Upon successful registration you should see the welcome message, the form should be cleared, and the Register and Cancel buttons should be enabled, as in Figure 23-1.
Figure 23-1
Summary In this chapter you examined alternative methods for notifying views of the results of procedure calls. These included using the ViewHelper and ViewLocator classes included with Cairngorm and using the Responder class found in the mx.rpc package. You then updated the registration feature to use the Responder class to act as an IResponder to do what you previously did by binding to a variable indicating the registration status on the ModelLocator.
257
Chapter 23: Calling Methods on Views This method of notifying views can reduce the number of properties in the ModelLocator that are simply there to notify views about the status of procedure calls. Revising the other features that are currently binding to status indicators on the ModelLocator is simply a matter of updating the events, commands, and views associated with those features, as you did for the registration feature. Let’s take a moment to review some key points from this chapter. The ViewHelper and ViewLocator classes have been deprecated, and their use is generally considered bad practice. One possible reason for their deprecation, and the primary argument against their use, is that they increase the coupling between classes (contrary to the claims made in the documentation). However, you may find older Cairngorm projects that use them, so it is worth knowing what they are and what their intended use is. The Responder class allowed views to behave as IResponders without having to directly implement the IResponder interface. This allows your views to respond to multiple types of events, since they can simply specify different responder functions for each event they dispatch. In order to use the Responder class, your events and commands had to be modified to take in responders. The responder instance was made optional, so views that do not need to respond to procedure calls do not have to specify one. This is the last chapter in which you will be working with the sample project. Subsequent chapters are devoted to topics such as criticisms of Cairngorm, best practices, and some of the tools that are available for working with Cairngorm. These chapters will be more informative now that you have completed the sample project.
258
Sequencing Commands In this chapter you will examine a method of triggering sequential commands. Sometimes you need a particular action to trigger a sequence of commands in which each command depends upon the success of the previous one. For example, when a user adds a comment in the sample application, you want the comment lists to be refreshed to display the new comment. Currently you arrange this directly by dispatching a new CommentEvent of type LOAD in the AddCommentCommand class. This works, but as you have seen in the last few chapters, there is often more than one way to accomplish the same result in Cairngorm. In this chapter you will examine another method of performing sequential operations, using the Cairngorm SequenceCommand class.
The SequenceCommand Class The makers of Cairngorm took into account that you might want to trigger sequences of commands. In the com.adobe.cairngorm.commands package you can find a class named SequenceCommand. This class contains a property named nextEvent that holds an instance of a Cairngorm event. You can fire this event by calling the executeNextCommand method. To use the SequenceCommand class you have your command class extend it, as in the following, rather than simply implementing the ICommand interface: package com.FlexBlog.commands { import com.adobe.cairngorm.commands.SequenceCommand; import com.adobe.cairngorm.control.CairngormEvent; import com.FlexBlog.events.SomeEvent; import mx.rpc.IResponder; public class ExampleSequenceCommand extends SequenceCommand
Chapter 24: Sequencing Commands implements IResponder { public function ExampleSequenceCommand() { this.nextEvent = new SomeEvent(SomeEvent.SOME_CONSTANT); } override public function execute(event:CairngormEvent):void{ //call some function on a delegate. } public function result(data:Object):void { //if all goes well, execute the next command this.executeNextCommand(); } public function fault(info:Object):void { } } }
Note one thing: now that you are extending a class rather than implementing the ICommand interface, you have to override the execute method. Also note that you are now defining a constructor that sets the nextEvent property by creating the appropriate event. This event is then triggered in the result function by calling the executeNextCommand function.
Revising Commenting To understand the use of the SequenceCommand class, you are now going to revise the commenting system to use this class to refresh the comment lists when someone makes a comment. Make the following modifications to the AddCommentCommand:
1.
Change the class definitions to extend SequenceCommand and implement IResponder: public class AddCommentCommand extends SequenceCommand implements IResponder
2. 3.
Make sure Flex updated the import statements for you. Add a constructor that assigns a CommentEvent to the nextEvent property. Note that you are only creating the event here and not dispatching it: this.nextEvent = new CommentEvent(CommentEvent.LOAD, this.model.currentPost); }
4.
Add the override keyword to the execute function: override public function execute(event:CairngormEvent):void
260
Chapter 24: Sequencing Commands 5.
Update the result function to call the executeNextCommand method rather than directly dispatching a CommentEvent: public function result(data:Object):void { var evt:ResultEvent = data as ResultEvent; if (evt.result.added == 1){ this.model.commentSubmissionStatus =’success’ this.executeNextCommand(); } }
6.
Debug the application, log in with the poe account (user name ⫽ poe, password ⫽ password) and make a new comment on the currently displayed post. Your screen should look like Figure 24-1, with the comment you made showing up in the list.
Figure 24-1
If you did not get the expected result, compare your AddCommentCommand to the one found in the source files for this book in /Resources/code/ch24/.
Summary In this chapter you examined a method of sequencing command calls by using the SequenceCommand class. You revised the commenting feature of the sample application to use this class to update the list of comments after one has been submitted. You can make similar modifications to the add posts feature and the loading of comments when a post is selected if you so desire.
261
Criticisms of Cairngorm Like most things in life, Cairngorm has both its fans and critics. In this chapter you will examine some of the criticisms leveled at Cairngorm.
Criticisms of Cairngorm First a little background on my experience learning to use Cairngorm. My first exposure to Cairngorm was at a local Flex user group meeting. I did not go there specifically to learn about Cairngorm; that just happened to be the topic. During the meeting there was lots of discussion about the strengths and weaknesses of the framework, which of course made no sense to me since that was the first time I had even heard of it. But from what I was hearing there seemed to be a lot of people who were not that wild about it. Some time later, Yakov Fain (who is well known in the Flex world; you can read his blog at http://flexblog.faratasystems.com/) was giving a talk to that same user group comparing several Flex frameworks, one of which was Cairngorm. His presentation featured a lot of good diagrams and the comparison made things a bit clearer, but I still had never done a project using Cairngorm and after all the negative things I had heard, I shied away from learning it. It was not until I had started using Cairngorm that the criticisms made sense. This is not to say that I necessarily agree with all of them, but I could at least see where they were coming from. For this reason I specifically left this chapter until after you had completed the sample project. Having even that little experience (assuming this has been your only experience with Cairngorm) gives you something through which to filter the issues that we will be discussing in this chapter.
Chapter 25: Criticisms of Cairngorm
Data Binding Data binding is not unique to Cairngorm. However, it is the primary mechanism for updating views using the ModelLocator. Putting a lot of bindable data in a single class can create performance problems for your application. So while these criticisms are equally valid for data binding in general, they do have particular relevance to Cairngorm. Data binding is a nice convenience feature. It is relatively simple for the programmer writing the code to tie properties on a view to a variable with data binding — then, magically, the views change whenever the data does. In many languages the developers themselves have to write the code to update the view. As with many of the nice features of many programming languages, “nice” does not come without a cost. While you, the developer, no longer have to write the code to update the views, this is only because Flex does it for you. A lot of code gets generated behind the scenes to make data binding work. If you are unfamiliar with what actually goes on when you use data binding, a description can be found in the Adobe Live Docs at http://livedocs.adobe.com/flex/3/html/databinding_2 .html#162751, which reads as follows: Binding occurs under the following circumstances: 1. The binding source dispatches an event because the source has been modified. This event can occur at any time during application execution. The event triggers Flex to copy the value of the source property to the destination property. 2. At application startup the source object dispatches the initialize event. All data bindings are triggered once at application startup to initialize the destination property. To monitor data binding, you can define a binding watcher that triggers an event handler when a data binding occurs. For more information, see Defining binding watchers (http://livedocs.adobe
.com/flex/3/html/databinding_7.html#197994). The executeBindings() method of the UIComponent class executes all the bindings for which a UIComponent object is the destination. All containers and controls, as well as the Repeater component, extend the UIComponent class. The executeChildBindings() method of the Container and Repeater classes executes all of the bindings for which the child UIComponent components of a Container or Repeater class are destinations. All containers extend the Container class. These methods give you a way to execute bindings that do not occur as expected. By adding one line of code, such as a call to the executeChildBindings() method, you can update the user interface after making a change that does not cause bindings to execute. However, you should only use the executeBindings() method when you are sure that bindings do not execute automatically.
So while it seems like a relatively simple thing to add a Bindable metadata tag and some curly braces to your code, there is actually a lot going on in the background to make everything happen. As you add more and more bindable properties to a project, more of this magic needs to occur. Since this is the primary mechanism used by Cairngorm to notify views of updates to the model, this can add a lot of weight and overhead to your projects.
264
Chapter 25: Criticisms of Cairngorm
Singled Out — Bad Singleton, Bad One of the most common criticisms (or at least one I keep hearing) is of Cairngorm’s use of singletons. A search for “Flex Cairngorm criticisms” turns up an article at http://nwebb.co.uk/blog/?p=168, which states: The main reason people tell me they dislike Cairngorm is due to its over-reliance on the Singleton design pattern, and I understand. Singletons increase coupling between classes and make them difficult to unit test. Any class which retrieves a singleton instance becomes unnecessarily coupled to the fact that the class is a singleton — it treats it in a different manner from other classes (i.e., it uses an uncommon instantiation technique). Singletons can’t be substituted without changing the source of the class which uses it, nor can they be swapped-out for an interface, because singleton instances are retrieved/instantiated with a call to a static method (getInstance() by convention) and you can’t have a static method on an interface. This limits polymorphism, going against the OO maxim, “Program to an interface, not an implementation.” Furthermore there is the issue of misplaced responsibility, as classes should not be responsible for limiting their own instantiation; that should be the responsibility of a factory or builder object.
The article goes on to suggest that a possible solution is passing references to the model around. So in other words, instead of this: var model:SomeModel = SomeModel.getInstance();
You would do something like this: public function set model (m:SomeModel){ this._model = m; }
The key difference is that the second method does not require the implementing class to know that the model is a singleton. The downside, of course, is that you now have to pass the model down the food chain. For example:
265
Chapter 25: Criticisms of Cairngorm This is the RegisterView from the sample project. In order to pass the model around, the view housing the RegisterView would have to use the setter to assign the model, which would then be passed down to the RegistrationForm view and so on down the chain. If the model is to remain a singleton, then the main application (or some other class) has to invoke the getInstance method and pass the reference down the chain. Some claim this is akin to having a global variable and is no better (or worse) than using the singleton. This idea of singleton vs. global code is explored in a blog post entitled “Singletons — we’re better off without them” by Richard Lord (http://www.bigroom.co.uk/blog/better-without-singletons). It states: I was first introduced to the singleton pattern as an alternative to global variables (“global variables are bad, use a singleton instead”). But this is one instance where singletons emphatically should not be used. Replacing a global variable with a singleton is just a lazy way of avoiding global variables without avoiding any of the problems inherent in global variables.
He then goes on to outline some of the problems associated with the use of global variables. These include: ❑
Inadvertent changes can occur: You may change the value of a global variable in one place and then think that it has remained unchanged elsewhere.
❑
Multiple references may be made to one object: You may refer to the same value by two names and not realize they’re the same variable.
❑
They inhibit code reuse: Classes that depend on the global variable can be used only in applications in which the global variable exists.
❑
They break modularity: All classes that use the global data are inherently tightly coupled since they all depend on what the other classes do with the global data.
❑
They make testing difficult: Try persuading a class to use a mock object instead of the global without changing the code in the class.
Lord then points out that many of the same issues exist with singletons, as they, too, are more or less global repositories of data. Of the issues he outlines, he states that you can avoid the first two simply by being careful. The others, he contends, are symptomatic of global data, in singleton form or otherwise. So what are the alternatives to singletons? Lord proposes that one possibility is the monostate pattern. The basic idea of a monostate class is that all the data is stored in static properties. This means that all instances of the class share the data, but, unlike a singleton, a monostate class can be instantiated like any other class; thus, any other classes that use it don’t need to use any special method of creating one, as is required with the singleton getInstance method. Lord provides the following example of a monostate class: package { public class Monostate { private static var _mono:SomeClass = null; public function get mono():SomeObj
266
Chapter 25: Criticisms of Cairngorm { if( _mono == null ) { _mono = new SomeClass(); } return _mono; } } }
In this example you can see that the data is held in private static variables that are then accessed via getter methods. However, that option is not without its critics as well. In a blog post titled “Architectural Atrocities, Part 9: Cairngorm’s Model Locator Pattern” by Theo Hultberg (http://blog.iconara.net/2008/04/13/ architectural-atrocities-part-x-cairngorms-model-locator-pattern/), the author has the following to say about the monostate pattern (take note that in these passages the author is referring to an older implementation of the ModelLocator): You might have noticed that I don’t use the term “singleton” in the discussion above. The reason is that the Cairngorm Model Locator implementation as it’s described in the introductory articles isn’t an example of the Singleton pattern, but Monostate (newer versions of the framework do this a little different, but I’ll get to that). This is how it’s introduced in the articles: “Having all the attributes on the Model Locator pattern as static attributes ensures that the Model Locator pattern is a simple implementation of a singleton.” From Adobe Developer Center: “Developing Flex RIAs with Cairngorm Microarchitecture — Part 2: Keeping State on the Client.” The Monostate (anti-)pattern can be described as a class that has only static members so that all instances of it share the same data. In most real-world examples of Monostate, instances aren’t even created but the class itself is used as an instance. There are numerous examples in the Flash APIs, for example, ExternalInterface and Mouse, but most were thankfully cleaned out in the transition to ActionScript 3. In an all-static class (such as the one in Model Locator) [there] is nothing but a namespace (i.e., package in ActionScript). It serves only the purpose of bundling together a number of variables and functions. This is indeed an anti-pattern and a regression to procedural programming. However, I should say that it seems like the recommendations have changed since the introductory article was written. In the latest version of the Cairngorm Store example the Model Locator is a proper singleton and has no static members except for some constants. This is just a change in implementation — the model is still used as a global variable.
As you can see, there are a lot of criticisms about the singleton design pattern and various views on possible solutions for its problems. Cairngorm is not alone in its use of singletons. One of the other mainstream frameworks used for Flex development is named PureMVC (another popular framework that can be used for Flex: http:// puremvc.org/). PureMVC also has several singletons at its core.
267
Chapter 25: Criticisms of Cairngorm Currently singletons are part of the Cairngorm framework and it is up to you and your team to decide how you want to deal with them. One way to deal with them is to keep direct references to the ModelLocator limited to one level of nesting in your views. In views that are nested deeper than one level, the data from the ModelLocator can be passed in as needed. This allows the deeper views to remain more flexible and reusable without relying on the Singleton interface.
I Have To Write How Many Classes To Do That? I am sure you have noticed from the sample application that doing anything in Cairngorm requires writing several classes. Even the simple features that you created for the sample project required 61 classes using the one-to-one paradigm. You were able to get this down to about 50 classes by combining events and delegates. Regardless of how you are handling events and delegates, as the number of features grows so does the number of classes. Most programmers are familiar with the “Hello World” application. There is a Cairngorm version of it at http://www.asfusion.com/assets/content/exampleFiles/flex/helloworld_cairngorm/ srcview/index.html. This entire application could be done in a few lines of standard Flex code, but
it takes eight Cairngorm classes and views to accomplish. Ending up with several classes to do even something as simple as saying “Hello World” is not unusual in an architectural framework. The same criticism regarding class overload is often leveled at PureMVC. Remember that the goal of an architectural framework is to provide a defined way of building an application, often to facilitate team development, not to end up with the fewest possible lines of code and classes.
Summary In this chapter you examined some of the criticisms leveled at Cairngorm. These noted the expense of data binding as the main mechanism for notifying views, the “over-reliance” on singletons, and the fact that Cairngorm projects require a large number of classes to be created even to do something relatively simple. There is no such thing as a perfect system. All systems have strengths and weaknesses. There is nothing that says you have to use all the parts of Cairngorm or use it at all.
268
Chapter 25: Criticisms of Cairngorm Steven Webster of Adobe Consulting is one of the authors of the six-part article “Developing Flex RIAs with Cairngorm Microarchitecture” (www.adobe.com/devnet/flex/articles/cairngorm_pt1 .html). I used his article as one of the sources for this book and it’s referenced in numerous articles about learning Flex. Webster also wrote an article titled “Why I Think You Shouldn’t Use Cairngorm” (http://weblogs.macromedia.com/swebster/archives/2006/08/why_i_think_you.html). The basic idea expressed in the article is that Cairngorm may or may not be appropriate for your project — this is something you need to determine before deciding to use it. Cairngorm (or any framework) is a tool. Just as using a sledgehammer to put a thumbtack in the wall may not be the best solution, Cairngorm may not be the right solution for your project or problem. There seems to be some idea in the development world that using a framework or a design pattern makes a person a better developer. I know that when I first started learning about the field I thought that “real developers” used design patterns. So I started looking for places I could use them because I wanted to, not because they were the best solution. I sometimes still find myself doing that and when I do, the solutions I come up with are generally overly complex. I would argue that it’s not using a given framework or design pattern that makes a better developer, but rather knowing when it is appropriate to use that framework or design pattern. You (or your team) need to honestly assess what challenges you are facing and evaluate what tools are available to best address them.
269
Best Practices When the term “best practices” comes up (in a discussion of any topic), there is usually a lot of debate as to what is and is not a best practice. In fact, depending on the source, you may find “best practices” that directly contradict one another (as you will see shortly). In this chapter you will examine two sources of “best practices” that regularly come up in a search for the terms “Cairngorm tips tricks ‘best practices.’” The first is Jesse Warden’s “10 Tips for Working with Cairngorm” (http://jessewarden .com/2007/08/10-tips-for-working-with-cairngorm.html) and the second is a presentation given at Adobe MAX 2008 by Eric Garza and Peter Martin of Adobe Consulting, titled “RIA Development with Cairngorm: Tips from the Experts” (https://share.acrobat.com/ adc/adc.do?docid=e2120738-1a70-40ff-b5d3-c5231346b466).
Ten Tips Jesse Warden opens his “10 Tips For Working with Cairngorm” blog post by stating, “These are not facts, just my opinions based on my experiences using ARP & Cairngorm for over two years.” As you will see, some of these are not so much tips or suggestions as they are reassurances that you are not alone in what you are experiencing while trying to learn Cairngorm. Warden proposes the following 10 tips for working with Cairngorm:
1. 2. 3. 4. 5.
If Cairngorm looks complicated, don’t worry. It is, and you’re not alone. If it feels like it takes a long time to do something in Cairngorm, it does. Only Commands set data on the Model; you can break this, just don’t if you can help it. Delegates make the server call and parse the data (sometimes in a Factory). If you see a Command parsing data, it’s coded wrong.
Chapter 26: Best Practices 6. 7. 8. 9. 10.
There are three ways to use Commands and Delegates. ViewLocators are considered bad practice. ViewHelpers are considered bad practice. Don’t use flash.net.Responder, use mx.rpc.Responder instead. Try not to have Views use CairngormEventDispatcher (or Events that extend CairngormEvent use event.dispatch()).
A few of these items contained subordinate items that I omitted from the listing. They’ll be taken into account later as we examine each of these tips. In the sections that follow, the heading will paraphrase the idea behind a given tip and tips may be combined if they are related.
Cairngorm Looks Complicated About this, Warden states, “I felt ‘comfortable with Cairngorm’ on my fourth project with it. To this day, I still mod it though, and try different things. As more people come into Flex, there are more cool ideas and techniques floating around.” I would count this as more of a reassurance than a given practice. However, it is a good point. Cairngorm may not make sense the first few times you try it, and like anything new it takes practice and fine tuning to use well.
It Takes a Long Time To Do Things with Cairngorm As Warden puts it, “ . . . three classes just to execute what would take me one line in Flash? Lamesauce!” This is related to the criticism that Cairngorm requires too many classes to do things that could be done in a few lines of code. We have already addressed this issue in the last chapter, but to reiterate, an application framework’s goal is to facilitate team development by providing standardized ways of doing things, not to produce the fewest lines of code. It may take some time to get used to the idea that in order to do seemingly simple things you have to create several classes, but the idea here is that several people working on the same project can all produce code that will play nicely together.
Only Commands Update the Model What is really being expressed here is a mandate to resist the temptation to just have an action on a view directly update the model, as that would be a violation of the MVC paradigm. If views are directly interacting with and updating the model, then you have code scattered across the application that could be the potential source of bugs or data corruption. However, if interaction with the model is limited to the control layer, then you know exactly where to look for code that could be improperly updating the model for a given operation.
272
Chapter 26: Best Practices Remember that the basic idea behind the MVC paradigm is to separate the responsibility of code into tiers. If you start mixing the interaction of those tiers (e.g., with direct interaction between the view and model) you defeat the purpose of using the paradigm to begin with.
Delegates Make Service Calls and Parse; Data/Commands Shouldn’t Parse Data Here points four and five are being combined, since they are related in the sense of what is responsible for parsing data, and these particular items sparked off a lot of discussion in the comments on this post. But before we get to that, let’s deal with the accessing of services. In general, delegates are used to access services. However, some argue that this is not strictly necessary and that delegates simply add an unnecessary layer to the logic. Again, it is up to you to decide if you find them useful. The idea that commands should not be parsing data is a precautionary measure against backend data format changes. The argument here is that your commands should deal with value objects; the delegate should be responsible for parsing raw data returned by the server and return the expected value object. For example, take the LoadRecentPostCommand, KeyWordSearchCommand, and CategorySearchCommand classes from the sample application. All these commands update the model with a collection of PostVO objects. Imagine that each of these classes was directly parsing the data returned from the service. If the format of that data changed (assuming you are not using some parsing utility class), you would have to update the parsing routing in all these commands. On the other hand, if they are all using a delegate class that handles parsing the data from the server, only the delegate class would have to be updated. However, one could simply encapsulate the parsing process in a utility class and use that anywhere the data needed to be parsed. Doing that would still require only updating the single parsing class and would not require the delegate to parse the data. Parsing data in the delegate also has implications for how the responder property of the delegate has to be used. If you are to parse the data in the delegate, then the delegate has to be the responder for the service call. It must parse the data in its result function and then call the result function of the responder, passing it a new ResultEvent with the parsed data. In other words, the command cannot be a direct responder of the service call. This approach would look as follows (adapted from David Tucker ’s blog post at http://www.davidtucker.net/2007/11/30/getting-started-with-cairngorm%E2%80%93-part-5/): package net.somproject.delegates{ import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.AsyncToken; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import com.someproject.parsers.SomeDataParser; public class SomeDelegate { private var responder:IResponder; private var service:Object; public function SomeDelegate( responder:IResponder ) { this.responder = responder; this.service = ServiceLocator.getInstance().getHTTPService(
273
Chapter 26: Best Practices “someService” ); } public function getSomething():void { // Get the AsyncToken var token:AsyncToken = service.send(params); /* Create an instance of the mx.rpc.Responder class * using the delegate as the responder */ var responder:mx.rpc.Responder = new mx.rpc.Responder(onResult, onFault); // Add the Responder to the AsyncToken token.addResponder(responder); } private function onResult( e:ResultEvent ):void { /* Parse the data from the server into the expected * valueobject for the command */ var data:Array = SomeDataParser.someParsingFunction( e.result as String ); // Pass parsed data back to Responder this.responder.result( new ResultEvent( ResultEvent.RESULT, false, true, data ) ); } private function onFault( e:FaultEvent ):void { ... } } }
Take note that this example also assumes that you are using some sort of parsing utility class. You may or may not do that, but the central idea remains the same: the delegate parses the data and sends back the expected value object. It would seem that the heart of the issue here is centralizing data parsing so that if the data format changes, you are required to update the least amount of code. This seems equally attainable by using parsing utility classes, which, as you can see in code example above, even the delegates make use of when parsing data. Hence the argument that only delegates should be parsing data is really only one possible method of dealing with data format changes by centralizing data parsing code.
Three Ways To Use Events, Commands, and Delegates This is one of the items that in the original post contained subordinate items a, b, and c, which are (paraphrased):
a. b. c.
274
You can create a one-to-one correspondence of events, commands, and delegates. You can combine related events and delegates. You can implement some variation of the previous list item.
Chapter 26: Best Practices In the sample application you have done items a and c. For item b, Warden has the command class using switch statements to determine which function on the delegate to call. You avoided having to do this in the sample application by having a discrete command class for each event type. Of these methods of working events, commands, and delegates, the first results in the simplest code in each of the individual classes as it does not require logic in the command class to determine what type of event was triggered. The tradeoff is that you end up creating more classes. The last two methods are aimed at reducing the number of classes, but have the tradeoff of making the code within them more complex. So what is really being asked here is what is more important: having fewer classes, or simpler code?
ViewLocators and ViewHelpers Are Considered Bad Practice Here points 7 and 8 are being combined. This topic was addressed in Chapter 23, “Calling Methods on Views.” These classes have been deprecated and their use is considered bad practice because they require the control layer to have knowledge of the particular functions to be called on a view.
Use mx.rpc.Responder Instead of flash.net.Responder Warden’s blog post is not entirely clear on why this is a recommendation; however, since delegates are most often used to access remote services, mx.rpc.Responder is the class that those services would be expecting anyway. Warden does point out the same issue that we discussed in Chapter 23 on notifying views: Flex does seem to have a problem distinguishing between classes with similar names.
Don’t Have Views Use Cairngorm Events Warden’s suggestion here is that you not have your views directly dispatch Cairngorm events, but rather dispatch custom Flex events that get bubbled up to a central controller in the application that dispatches the Cairngorm events. The argument here is that doing this allows your components to be more reusable and flexible. One could counter-argue that this essentially adds another FrontController to the mix, but it also makes it possible for your components to be reused in non-Cairngorm projects.
275
Chapter 26: Best Practices
RIA Development with Cairngorm: Adobe Max Presentation Eric Garza and Peter Martin of Adobe Consulting laid out their ideas for best practices in a presentation titled “RIA Development with Cairngorm: Tips from the Experts” given at Adobe MAX in 2008. I’ve chosen to cover some of their tips that either supplement or provide some contrast with those put forth by Jesse Warden. Garza and Martin suggest that commands should not reference the model directly. Instead they suggest that callback functions be provided on the event and that these responder functions be used to update the model. This involves the creation of additional responder classes, which would look something like what follows. A view dispatches an event that registers responder functions: public function loadData() : void { new SomeEvent(resultHandlerFunction, faultHandlerFunction).dispatch(); }
Commands then pass these responder functions on to the delegate by creating one of the custom responder classes: public function execute(event:CairngormEvent):void{ var evt : SomeEvent = SomeEvent( event ); var delegate : SomeDelegate = new SomeDelegate( new SomeCustomCommandResponder (evt.resultHandler, evt.faultHandler ) ); delegate.getData(); }
Garza and Martin also recommend the use of the SequenceCommand class for triggering multiple commands from a single user gesture and for delegating classes to access services. They make several suggestions regarding the use of the ModelLocator. These are: ❑
Use data binding to notify views.
❑
Call getInstance in the view once and pass that instance to your other views and classes.
❑
The ModelLocator should store only strongly typed data classes (e.g., value objects) and not simple properties.
The first two points have already been addressed elsewhere in this book. The third point really means that you should use value objects to group related properties and then store instances of those on the model, rather than storing the individual properties. So rather than having properties on the model like this public var userFirstName:String public var userLastName:String
276
Chapter 26: Best Practices you instead have these properties inside a value object, and store an instance of that object in the model, like this: public var user:UserVo
Garza and Martin also have suggestions for the FrontController, which include: ❑
Grouping the registration of similar commands into functions that then get called.
❑
The possible use of sub-controller classes, which are then used to compose the larger controller class.
What they are suggesting here is more or less an organization principle that will avoid your having one long list of addCommand calls in the constructor of your FrontController. For example, with the sample application you could have a function in the FrontController such as the following: public function addPostCommands():void{ addCommand(PostEvent.ADD,AddPostCommand); addCommand(PostEvent.LOAD, LoadRecentPostsCommand); addCommand(PostEvent.VIEW, SetCurrentPostCommand); }
The second suggestion is merely an extension of this idea: group related commands into sub-controller classes and your main controller can construct those classes as necessary. Using a methodology such as this makes it much easier to track down where a given command is registered, since you do not have to search through one long list of addCommand calls.
Summary In this chapter you examined some of the ideas that are out there in regards to “best practices” for using Cairngorm. You may have noticed that some of the “best practices” put forth by Eric Garza and Peter Martin are in direct opposition to those put forth by Jesse Warden (hence the quote marks around the term). For example, Warden’s third tip is “only commands set data on the model,” while Eric Garza and Peter Martin state “commands should not reference models directly.” If commands are not referencing the model, it is going to be quite difficult for them to update it as Warden suggests they should be doing. My point here is not to imply that any of these people are being dishonest in any way or that they do not know what they are talking about. My point is that when anyone puts forth an idea about “best practices” what is generally being said is, “This is what has worked for me.” As can be seen from the revisions you made to the sample application to combine event and delegate classes, and from some of the code samples used by Garza and Martin, there are different ways of using Cairngorm. Whether a given best practice is going to work for you may depend heavily on how you are using the framework to begin with.
277
Chapter 26: Best Practices Both Warden and Garza/Martin make some good points that should be taken into consideration. You are going to have to evaluate these ideas and any others on “best practices” that you may find and decide which ones make sense for your project. If you are using a framework such as Cairngorm, it is likely that you are doing so to gain the benefits that come from the organization provided by Cairngorm to facilitate team development. One of the key issues when working in teams, whether using a framework or not, is ensuring that all members of the team are coding in a consistent and compatible manner. As you have seen in the examples in this book, despite the organization provided by Cairngorm, there are still numerous ways that one can use the framework. The relative nature of “best practices” aside, here are some of my suggestions. When making the decision to use the Cairngorm framework, get together as a team and decide the following: ❑
What package structure will you use? Make sure everyone is aware of the package structure and that it is understood that any changes to the structure must be agreed upon by the team as a whole.
❑
How are you going to use event, command, and delegate classes? Will you be using the oneto-one correspondence between event, command and delegate classes, or will you be combining similar events and services calls into related event and delegate classes?
❑
How will value objects be used? What kinds of value objects are there and what properties do they have?
The real issue being addressed in the above points is to make sure that everyone on the team is in agreement as to how things will be done. This will result in more consistent and compatible code, which is the major motivation for using Cairngorm in the first place.
278
C airngorm Plug -in In this chapter you examine the Cairngorm Eclipse plug-in, including its installation and basic usage. Eclipse is the program that Flex Builder is built on. The basic documentation for this plug-in can be found at http://opensource.adobe.com/wiki/display/cairngorm/Using. For the most part the instructions in this chapter will follow those outlined in the documentation. However, there are a number of common issues that you may run into when trying to install and use the plug-in, which are not covered in the documentation, but are covered in this chapter. The Cairngorm plug-in is an Eclipse plug-in that provides additional tooling to improve productivity for developing with Cairngorm. The initial focus of the Cairngorm plug-in is on the Controller. The aim is to improve productivity by removing the repetitive action of creating a new Command and associated Event and adding them to the Controller. This is accomplished by a new class wizard for the following Cairngorm artifacts: ❑
Controller: Creates a new controller.
❑
Command: Creates a new Event and a new Command and adds them to the Controller.
The class templates used for the codegen are externalized and distributed with a specialized version of the Cairngorm SWC. This enables you to modify the templates to support your own code style.
Installation The first thing to note is that the use of this plug-in requires a special beta version of the Cairngorm SWC, which can be downloaded from http://download.macromedia.com/pub/ opensource/cairngorm/plugin/cairngorm2_2_2beta-bin.zip. Download this file and unzip it to a location that you can remember.
Chapter 27: Cairngorm Plug-in The second thing is that, while it’s not hard to use the Eclipse update manager, if you’ve never done it before, the instructions at the Cairngorm plug-in website are missing a few helpful details. These have been included in the instructions in this chapter. You follow these steps to install the Cairngorm plug-in from an Eclipse update site:
1.
Within FlexBuilder go to Help
Software Updates
Find and Install . . . (Figure 27-1).
Figure 27-1
2.
Choose “Search for new features to install” and click Next (Figure 27-2).
Figure 27-2
280
Chapter 27: Cairngorm Plug-in 3.
Make sure the “Ignore features not applicable to this environment” and “Automatically select mirrors” options are checked and then click New Remote Site . . . (Figure 27-3).
Figure 27-3
4.
Enter a name (e.g., Cairngorm Plugin) and the following URL: http://download .macromedia.com/pub/opensource/cairngorm/plugin/. Then click OK (Figure 27-4).
Figure 27-4
281
Chapter 27: Cairngorm Plug-in 5.
Make sure that the site you just added and the Europa Discovery Site are checked, as in Figure 27-5, and click Finish.
Figure 27-5
6.
In the Updates dialog select the Cairngorm Plugin site. If you see the error shown in Figure 27-6, expand the Europa Discovery Site and click Select Required. The message should disappear and the Updates dialog should look like Figure 27-7. Click Next to continue.
Figure 27-6
282
Chapter 27: Cairngorm Plug-in
Figure 27-7
7.
Agree to the terms and click Next (Figure 27-8).
Figure 27-8
283
Chapter 27: Cairngorm Plug-in 8.
Click Finish to begin the installation (Figure 27-9).
Figure 27-9
9.
When Flex has completed downloading the updates, you should see the dialog in Figure 27-10. Click Install All to continue.
Figure 27-10
284
Chapter 27: Cairngorm Plug-in 10.
When the updates have finished installing, you should see the dialog in Figure 27-11. Click Yes to restart Flex and complete the update process.
Figure 27-11
Cairngorm Locations Once you have the plug-in installed, you need to configure your Cairngorm locations under your Flex Builder preferences. The Cairngorm location contains the templates required for code generation. To configure the locations, do the following:
1.
In Flex Builder, open the Preferences dialog (Mac: Flex Preferences, Windows: Window Preferences) and select Cairngorm, as shown in Figure 27-12.
Figure 27-12
285
Chapter 27: Cairngorm Plug-in 2.
Click Add . . . and you should see the fields in Figure 27-13.
Figure 27-13
3. 4.
Enter a name for the location, such as Cairngorm 2.2 Beta, in the Name field.
5. 6.
Select the cairngorm2_2_2beta-bin folder.
Click Locate and navigate to where you extracted the beta version of Cairngorm that you downloaded earlier.
Click Finish. The location should now appear in the list, as in Figure 27-14.
Figure 27-14
286
Chapter 27: Cairngorm Plug-in 7.
Click OK to close the Preferences dialog.
Cairngorm Project Nature To use the Cairngorm tooling, you will need to add the Cairngorm Project Nature to your Flex project. To do this, right-click your Flex project and choose Add Cairngorm Project Nature, as shown in Figure 27-15.
Figure 27-15
According to the online instructions, the two dialogs seen in Figure 27-16 and Figure 27-17 (if you have an existing controller) should appear; however, they may not do so. If they do not appear you can still use the plug-in, but you will need to manually add the beta version of the Cairngorm SWC to your project (see Chapter 12 for instructions). In the Add Cairngorm Nature dialog (Figure 27-16), select the Cairngorm location that you created in the previous section and the library path to which you would like the Cairngorm SWC copied. Once you have done this, click Finish.
287
Chapter 27: Cairngorm Plug-in
Figure 27-16
If you have any existing controllers in the project, the plug-in may detect this and ask you which one you would like to use as the default controller. This is the controller to which the plug-in will add your command and event classes. Click the checkbox next to the desired controller and click Finish.
Figure 27-17
288
Chapter 27: Cairngorm Plug-in
Create a New Controller Before you can do anything else with the Cairngorm plug-in, you need to have a FrontController class. However, there is a known issue with this plug-in that may prevent the Cairngorm Controller and Cairngorm Command menu items from appearing in the New submenu (Figure 27-18), which you will need to be able to access before you can create the FrontController.
Figure 27-18
The documentation suggests that the only known workaround is to create a new workspace. However, this is not actually necessary. If the Cairngorm Controller and Cairngorm Command menu items do not appear for you, do the following:
1.
Right-click a blank area near the toolbars at the top of the application and select the Customize Perspective item shown in Figure 27-19.
Figure 27-19
2. 3. 4.
In the Customize Perspective dialog (Figure 27-20) select New from the Submenus drop-down.
5.
Click OK.
Click the checkbox next to Cairngorm in the Shortcut Categories list on the left. Select the Cairngorm Command and Cairngorm Controller items from the Shortcuts list on the right.
289
Chapter 27: Cairngorm Plug-in
Figure 27-20
Once you have done this, the Cairngorm Controller and Cairngorm Command items should show up under the New menu, as in Figure 27-18. In order to add commands, you will first need a controller to add them to. To create a new controller, do the following:
1.
Right-click your project in the Flex Navigator and select New (Figure 27-21).
Cairngorm Controller
Figure 27-21
2.
290
In the New Cairngorm Controller dialog (Figure 27-22) click the Browse button next to the Package field to specify the location to create the controller in.
Chapter 27: Cairngorm Plug-in
Figure 27-22
3.
Select the location from the Package Selection dialog (Figure 27-23) and click OK.
Figure 27-23
291
Chapter 27: Cairngorm Plug-in 4.
Enter a name for your controller and click Finish (Figure 27-24).
Figure 27-24
The result will be a FrontController class such as the following: package com.cairngormplugin.controllers { import com.adobe.cairngorm.control.FrontController; public class CairngormPluginController extends FrontController { // do not remove this constant as it is used by the Cairngorm Plugin private static const UUID : String = “a42e5c12-6f90-482d-bea6-0914cfb7c316”; public function CairngormPluginController() { initialiseCommands(); } private function initialiseCommands() : void { // TODO Auto-generated add your commands here } } }
292
Chapter 27: Cairngorm Plug-in Take note that when a new Controller is created it will have a UUID constant. Even though this is private, it is important that you do not remove it, as it is used by the project properties to associate the Controller with its default settings.
Cairngorm Project Proper ties You need to provide the Cairngorm plug-in with some defaults to use when creating event and command classes. To configure the Cairngorm plug-in for the project, do the following:
1. 2.
Right-click your Flex project and choose Properties. Choose Cairngorm to set your Cairngorm properties and you should see something like Figure 27-25.
Figure 27-25
In the Properties for CairngormPlugin dialog you can set the following: ❑
Default Command superclass: The default superclass that will be extended by a command. This is optional.
❑
Controller: The default controller to register the command with.
293
Chapter 27: Cairngorm Plug-in ❑
Function: The default function where the call to addCommand() will be inserted. This can be either the constructor or an initializeCommands function, which will be added for you when you create the controller using the plug-in.
❑
Event package: The default package where the Event class will be created.
❑
Command package: The default package where the Command class will be created.
Note that the controller class you created in the previous step is set as the default. You can modify the defaults for the controller as you like. The command superclass is optional. Should you desire to set it, click the Browse button next to the Command superclass field and select the desired class from the Open Type dialog shown in Figure 27-26. Click OK.
Figure 27-26
You do need to specify the package locations for the event and command classes. To do this, click the Browse button next to the appropriate package field, select the package from the Package Selection dialog (Figure 27-27), and click OK.
294
Chapter 27: Cairngorm Plug-in
Figure 27-27
The Properties for CairngormPlugin dialog should now look like Figure 27-28. Click OK to complete the process.
Figure 27-28
295
Chapter 27: Cairngorm Plug-in
Create a New Command Creating a new command actually does several things: ❑
Creates a command class.
❑
Creates an event class.
❑
Registers the event and command class in the default controller.
To create a new command, do the following:
1.
Right-click your project in the Flex Navigator and select New (Figure 27-29).
Cairngorm Command
Figure 27-29
2.
In the New Cairngorm Event and Command dialog (Figure 27-30) do the following:
a. b. c. d.
296
Enter a name for the event in the Event name field. Enter the event class name in the Event class field. Enter the command class name in the Command class field. Click Finish.
Chapter 27: Cairngorm Plug-in
Figure 27-30
The process generates the following classes: LoginEvent: package com.cairngormplugin.events { import com.adobe.cairngorm.control.CairngormEvent; import flash.events.Event; public class LoginEvent extends CairngormEvent { public static const EVENT_NAME : String = “login”; public function LoginEvent( bubbles : Boolean = false, cancelable : Boolean = false ) { super( EVENT_NAME, bubbles, cancelable );
297
Chapter 27: Cairngorm Plug-in } public override function clone() : Event { return new LoginEvent( bubbles, cancelable ); } } }
LoginCommand: package com.cairngormplugin.commands { import com.adobe.cairngorm.commands.Command; import com.adobe.cairngorm.control.CairngormEvent; public class LoginCommand implements Command { public function LoginCommand() { } public function execute( event : CairngormEvent ) : void { // TODO Auto-generated add your code here } } }
Additionally, it registers the newly created classes in the FrontController class: package com.cairngormplugin.controllers { import com.adobe.cairngorm.control.FrontController; import com.cairngormplugin.commands.LoginCommand; import com.cairngormplugin.events.LoginEvent; public class CairngormPluginController extends FrontController { private static var UUID : String = “a42e5c12-6f90-482d-bea6-0914cfb7c316”; public function CairngormPluginController() { initialiseCommands(); } private function initialiseCommands() : void { addCommand( LoginEvent.EVENT_NAME, LoginCommand ); } } }
298
Chapter 27: Cairngorm Plug-in
Customizing the Templates The classes generated in the previous examples are done so by means of JET (Java Emitter Templates), which is part of the EMF (Eclipse Modeling Framework). The templates that generate the classes are located in the /resources/flexbuilder/templates folder. These include: ❑
Controller.asjet
❑
Event.asjet
❑
Command.asjet
The Cairngorm Plug-in locates the templates through the project’s association to a Cairngorm location. It reads the config file (resources/flexbuilder/cairngorm.xml) to get the template to use. If you want to change the style of the generated code you can modify the existing templates, or better yet create new ones and update cairngorm.xml. The format used by JET is very similar to the JSP (Java Server Pages) syntax. The specifics of editing these templates are beyond the scope of this book. Should you be interested in learning more about how to edit Java Emitter Templates, you can check out these tutorials: ❑
JET Tutorial Part 1: http://www.eclipse.org/articles/Article-JET/jet_tutorial1 .html
❑
JET Tutorial Part 2: http://www.eclipse.org/articles/Article-JET2/jet_tutorial2 .html
Known Issues There are a few known issues regarding the Cairngorm plug-in. First, it may not be able to find existing controllers. When you create a controller using the plug-in, it gives it a unique ID using a private static constant UUID. The unique ID is used internally by the plug-in to associate the controller with its configuration. If a controller doesn’t have the UUID constant, the plug-in will ignore it. If you have an existing controller you will need to add the variable manually, but make sure the value is unique. Second, when you add a new command it is automatically added to the controller, and to enable this the plug-in uses the Flex Builder Code Model API to rewrite the controller with the new command added. There are some limitations to this system, as the plug-in will ignore the following: ❑
Code style
❑
The use of the implements keyword
❑
Metadata
❑
Function code blocks (if you need a custom function, put it in a base class)
299
Chapter 27: Cairngorm Plug-in
Summary In this chapter you examined the Cairngorm plug-in. The instructions contained in this chapter included additional information and are different from those found on the Cairngorm plug-in web site, in order to better address some problems that may come up. The following topics were covered in this chapter: ❑
Installation
❑
Creating a Cairngorm location
❑
Adding the Cairngorm Project Nature
❑
Creating a controller
❑
Cairngorm project properties
❑
Creating a command
❑
Known issues
Remember that to use the Cairngorm plug-in, you need to be using the beta version of the Cairngorm SWC that can be downloaded from the Cairngorm plug-in site.
300
Cairngorm Extensions In this chapter you are introduced to the Cairngorm Extensions created by Universal Mind (http://www.universalmind.com), which provide additional functionality to the base Cairngorm classes. The Cairngorm Extensions exist as an open source project on Google Code at http://code.google.com/p/flexcairngorm/. Portions of the code in this chapter are from the Flex Cairngorm SVN repository located at http://flexcairngorm.googlecode.com/svn/trunk/. Questions about use are addressed in the “Open Source Notifications” section in the Introduction to this book.
What Are They? Universal Mind has extended the Adobe 2.2.x Cairngorm version to provide additional functionality. These features are packaged in the CairngormExtensions.swc, which currently includes all the code from the standard Cairngorm.swc, so you need only include that SWC file in your project to get the functionality of both Cairngorm and the extensions. The Google Code site describes this additional functionality as follows: ❑
❑
Events ❏
Built-in support to transport responders for direct view or business logic callbacks
❏
Implementation of AnnounceFaultEvent to allow business logic to centralize error reporting and logging
❏
Implementation of EventGenerator to allow developers to automate dispatching of sequences of events
❏
Events that now should self-dispatch for direct delivery to the business/controller layer
View notification
Chapter 28: Cairngorm Extensions ❏ ❑
❑
❑
❑
Built-in framework support to allow views to use proxy responders to request direct notifications to business events responses
FrontControllers ❏
SubControllers available so modules implemented with sub-MVC are dynamically aggregated and used within a global MVC framework
❏
Improved error checking
❏
Easily register a function callback for any business event
Command implementation ❏
Base class implementation
❏
Enhancements to support aggregation of event-business logic within a single command class
❏
Support for the optional view notifications
❏
Best practice to deprecate command sequencing
Delegate implementation ❏
Base class implementation
❏
Support for easy queuing of delegate calls
❏
Improved support for WebService use and WSDL errors
❏
Best practice to allow easy data transformations
❏
Best practice to hide multiple server calls
ServiceLocator ❏
Configure services (RDS) at runtime
❏
Configure timeouts of HTTPServices and WebServices
Currently the links on the Google Code site that are supposed to provide additional details about these features are blank. Additionally, there is no direct link on the project home page for downloading the CairngormExtensions.swc. You have to check out the code from the SVN repository (http:// flexcairngorm.googlecode.com/svn/trunk/) to get it. Once you have done so, the SWC file can be found in the /code/build/ folder. Similarly, the Sample Application (a version of the Flex Store application) link does not actually lead you to anyplace where you can download the application built with the extensions; it contains only links to the original Flex Store and a Cairngorm version of the store. The space for the link to the Cairngorm Extensions version simply says “coming soon.”
302
Chapter 28: Cairngorm Extensions The Documentation and Resources link does have some PDF presentations that give you some information about how the extensions are intended to be used and the reasons behind creating them (these documents are also in the docs folder of the source code). However, many of these are points from PowerPoint presentations and lack context outside the live presentation. Given the current state of the documentation for the project, I’m not going to cover in detail the full list of features mentioned earlier, but rather some of the more prevalent features of the Cairngorm Extensions, which we will explore by examining the code from the CafeTownsend_CGX sample application found in the /docs/samples folder of the SVN repository. This application does not implement all the features of the Cairngorm Extensions, but does have enough to give a general idea of the differences between using them and the standard use of the Cairngorm framework. If you are interested in learning more about the Cairngorm Extensions, an interview with Thomas Burleson (one of the creators of the extensions) can be found at http://www.theflexshow.com/blog/ index.cfm/2008/4/9/The-Flex-Show--Episode-41-Universal-Mind-Cairngorm-Extensionsw-Thomas-Burleson.
Notifying Callers Many of the improvements made in the Cairngorm Extensions center on using the mx.rpc.IResponder interface to respond to asynchronous calls. The typical method of handling a remote service call in Cairngorm can be seen in Figure 28-1.
Async response
View
Command
Event dispatched
Delegate
Function called on delegate
Server
Async call
Figure 28-1
In this scenario, only the command class responds to the asynchronous call. This class then must update the ModelLocator to notify the rest of the application of any changes resulting from the call. The Cairngorm Extension modifies this process, as shown in Figure 28-2.
303
Chapter 28: Cairngorm Extensions Notify caller
View
Notify caller
Command
Event dispatched
Async response
Delegate
Function called on delegate
Server
Async call
Figure 28-2 In the Cairngorm Extensions system, the async response is sent back to the delegate class. When the delegate receives this response, each calling class notifies its calling class of the response, forming a chain of responders potentially reaching back to the component that dispatched the event. The result of this system is that classes, including views, can now respond to async calls by registering as responders. You saw examples of the basic principles underlying this process when you used the mx.rpc.Responder class in Chapter 23. In the Cairngorm Extensions, the responder methodology is actually built into the extensions. The extensions implement this responder methodology as follows: Notifications are sent back to the calling class using a function called notifyCaller implemented in the delegate and command classes. Callers register to be notified of the results of async calls using a class named Callbacks that implements the mx.rpc.IResponder interface. The Callbacks class is used in much the same way as the mx.rpc.Responder class, as can be seen in the code for the Callbacks class: package com.universalmind.cairngorm.events { import mx.rpc.IResponder; import mx.rpc.events.*; public class Callbacks implements IResponder { public static const PRIORITY_BEFORE : int = 0; public static const PRIORITY_AFTER : int = 1; public static const PRIORITY_OVERRIDE : int = 2; public var resultHandler : Function; public var faultHandler : Function; public var conflictHandler : Function; public var priority : int; public function Callbacks( resultFunc : Function, faultFunc : Function = null, conflictFunc: Function = null, priority : int = PRIORITY_AFTER) { this.resultHandler = resultFunc; this.faultHandler = faultFunc; this.conflictHandler = conflictFunc; } public function result(info:Object):void { if (resultHandler != null){
304
Chapter 28: Cairngorm Extensions resultHandler(info); } public function fault(info:Object):void { if (faultHandler != null){ faultHandler(info); } } }
Much like the mx.rpc.Responder class, the Callbacks class enables you to specify result and fault functions and includes parameters for a conflict function and priority integer. The Callbacks class implements the IResponder interface; the result and fault functions call the functions that are registered in the class constructor. The CafeTownsend_CGX sample project does not actually make use of the Callbacks class, but in the classes you can see the implementation that would enable you to do so. For example, the constructor for the LoginEvent looks as follows: public function LoginEvent(username:String,password:String, handlers:Callbacks=null) { super(EVENT_ID, handlers, true, false); this.username = username; this.password = password; }
Here the event constructor contains a parameter called handlers that takes an instance of a Callbacks class. Cairngorm Extension events extend the com.universalmind.cairngorm.events.UMEvent class. This class has the following variable used to hold the responder, which is set when the super-constructor is called in the extending event class: public var callbacks : IResponder = null;
Here you can see that the instance of the Callbacks class is actually typed as an IResponder. Since the event holds a reference to an IResponder, this IResponder now becomes available in the command class. Commands in the Cairngorm Extensions extend the com.universalmind.cairngorm .commands.Command class. The execute function for this class takes in an instance of a CairngormEvent and caches it as the responder, as shown in the following: public function execute(event:CairngormEvent) : void { cacheCaller(event); }
305
Chapter 28: Cairngorm Extensions The event acting as a responder is passed in by the execute function of the extending command class, as can be seen in the UserCommand class: public class UserCommand extends Command { override public function execute(event:CairngormEvent):void { super.execute(event);
Once this responder has been cached, command classes notify it of any results by calling the notifyCaller function, which is implemented in the com.universalmind.cairngorm.commands.Command class, which calls the responder functions on the cached event class. An example of calling the notifyCaller function can be seen in the login function of the UserCommand class: private function login(event:LoginEvent):void { var username : String = event.username; var password : String = event.password; if ( username == “Flex” && password == “Cairngorm” ) { __model.user = new User( username, password ); new LoadEmployeesEvent().dispatch(); } else { var error : Fault = new Fault(“”,”Login Failed”,”We couldn’t validate your username & password. Please try again.”); new AnnounceFaultEvent(error).dispatch(); __model.user = new User(); } notifyCaller(); }
This system of registering responders and notifying callers allows classes to respond to async calls without entirely relying on listening changes in the ModelLocator, as the system allows the response chain to work its way back up the chain of calling classes.
Event Generators Another one of the major features of the Cairngorm Extensions is the ability to generate events via a class called the EventGenerator. This is conceptually similar to the SequenceCommand class from the Cairngorm framework (Chapters 7 and 24) in that it enables you to specify a chain of logic. However, the EventGenerator class goes beyond the capabilities of the SequenceCommand class in that it enables you to specify a sequence of events that can then be triggered in series or parallel and do not require any special implementation in the command classes. The source code for the EventGenerator class (com.universalmind.cairngorm.events.generator) contains the following example of an EventGenerator implemented in MXML:
This example actually shows nested EventGenerators triggered both in parallel and consecutively, as indicated by the trigger attribute of the EventGenerator tag. When EventGenerators are nested in this manner, triggering the parent EventGenerator triggers all its child events. Triggering an EventGenerator is done via the dispatch method on the id of the EventGenerator. For example, to trigger the EventGenerator in the preceding code you would do the following: startUpEvents.dispatch();
Summary In this chapter you examined some of the more prevalent features of the Cairngorm Extensions created by Universal Mind. These included: ❑
Notification of calling classes of the results of an asynchronous call through the use of the Callbacks (IResponder) class
❑
The triggering of sequences of events in series or parallel by means of the EventGenerator class
While the documentation for this project is still in the development phase, you can learn more about how the extensions can be used by checking out the source code from the SVN repository at http:// flexcairngorm.googlecode.com/svn/trunk/, which is also currently the only place to get CairngormWithExtensions.swc.
307
Looking Back and Ahead In this chapter you’ll review the topics covered in this book. Then you’ll be introduced to some additional resources that you can use to further your Cairngorm skills. Finally, you’ll be given some suggestions about where to go from here.
Review In this book you took an in-depth look at the Cairngorm framework. Let’s take a moment to review the topics that were covered in this book.
Foundations of Cairngorm In Chapter 1 you were given a broad overview of Cairngorm, starting with a basic definition: Cairngorm is a lightweight micro-architecture for the rapid development of RIAs. Then you were given a brief history of Cairngorm, which goes as far back as Flash MX. Cairngorm evolved from principles and practices used in Java RIA development that became applicable to Flash with the introduction of remoting capabilities. Next you were given a broad overview of the packages that make up Cairngorm. These are: ❑
com.adobe.cairngorm
❑
com.adobe.cairngorm.business
❑
com.adobe.cairngorm.commands
❑
com.adobe.cairngorm.control
Chapter 29: Looking Back and Ahead ❑
com.adobe.cairngorm.model
❑
com.adobe.cairngorm.view
❑
com.adobe.cairngorm.vo
Then we looked at the major components of Cairngorm: ❑
ModelLocator
❑
ServiceLocator
❑
Commands
❑
Events
❑
FrontController
❑
Value objects
❑
Delegates
Next we examined the basic logic flow of Cairngorm, which can be summarized as follows: Some action causes a Cairngorm event to be dispatched. The FrontController intercepts this event and calls the execute function of a corresponding command class. The command class decides how the event will be handled. If remote data needs to be accessed, the command class uses a delegate to access the remote data. When any remote calls are completed, the command updates the ModelLocator. Changes to the ModelLocator are broadcast to the views via data binding. Then you were introduced to the various ways of organizing a project when using Cairngorm by looking at the FStop and Cairngorm store applications. While the package structures in these sample applications work and are valid, this book recommends the following package structure, as it is clearer where a given type of class is located: ❑
commands (stores command classes)
❑
controllers (stores FrontController classes)
❑
delegates (stores service delegate classes)
❑
events (stores event classes)
❑
models (stores ModelLocator classes)
❑
services (stores the ServiceLocator class)
❑
valueobjects (stores value object classes)
❑
views (stores MXML views)
Finally, we discussed the benefits of using Cairngorm. These included the facilitation of team development and easier maintenance, debugging, and adding or updating of features. These benefits arise from the structured way in which Cairngorm applications are built.
310
Chapter 29: Looking Back and Ahead Chapter 2 explored the concepts of frameworks and design patterns. Two types of frameworks were examined: application and architectural. Cairngorm is an architectural framework, as it provides a defined structure around which an application is built. Next the term micro-architecture was defined as a collection of design patterns that have been proven to work well together. While you will see Cairngorm referred to as both a framework and a microarchitecture, micro-architecture is the more specific and descriptive definition. We then examined the concept of a design pattern, which can be loosely defined as a general description of a solution to a common problem. Design patterns are not specific implementations, nor are they tied to any specific programming language. In fact, they originate in the world of architecture, which has nothing to do with computer programming. They simply describe a general solution that can be implemented in a variety of ways. We examined the following design patterns: ❑
Model View Controller
❑
Observer
❑
Singleton design
❑
Command design
❑
Proxy
The Major Components of Cairngorm In Chapters 3 through 9 we explored in more detail the major components of the Cairngorm framework defined in Chapter 1. You examined the actual source code that makes up the classes and interfaces for each component. Then you learned how to create application-specific instances of each component and examined samples from the FStop and Cairngorm store applications. The major purposes of the components of Cairngorm can be summarized as follows: ❑
The ModelLocator acts as a data store for global application data. Views subscribe to be notified of changes to the ModelLocator using data binding.
❑
The ServiceLocator provides a centralized location for creating and configuring remote service classes such as HTTPService, WebService, and RemoteObject.
❑
The FrontController serves as a registry of events and command pairings. Registering events and commands in the FrontController ensures that when a particular Cairngorm event is dispatched, the corresponding command is triggered.
❑
Cairngorm event classes trigger application logic by dispatching themselves to the CairngormEventDispatcher class. This causes the FrontController to call the execute function on the corresponding command class.
❑
Command classes decide how an event needs to be handled. These decisions can be as simple as updating the ModelLocator, or more commonly, calling a remote service using a delegate and then updating the ModelLocator based on the results of the service call.
311
Chapter 29: Looking Back and Ahead ❑
Value objects classes are used to transfer data around the application and the backend. These classes consist of related sets of public properties.
❑
Delegate classes are used to control access to remote data. These classes allow for a responder to be registered to handle the results of the remote service calls and provide related sets of methods to access services.
Chapter 10 then showed how these major components work together to form an application feature. In that chapter you examined a simple login application. In Chapters 11 to 13, you were introduced to some background details of the sample project. This included a broad overview of the features you created, the reason for creating an AIR application, setting up the project, and an overview of how the backend works.
The Sample Application In Chapters 14 through 20 you built several features of a blogging application. These features included: ❑
User registration
❑
Login and logout
❑
Adding of new posts
❑
Loading of existing posts
❑
Commenting system
❑
Search capabilities
In Chapter 21, you examined how the Cairngorm framework was used to build the sample application. In the initial build of the application you had a one-to-one correspondence of events, commands, and delegates. Then in Chapters 22 and 23 you examined alternative ways of building these features. You looked at combining event and delegate classes with related functionality and at using responders to notify views of the results of remote service calls, among other possibilities. In Chapter 24 you examined the use of the SequenceCommand class. This class allows a single event to trigger a series of other events. Each subsequent event can be triggered based upon the success of the previous event.
Criticisms and Best Practices In Chapter 25 you examined some of the criticisms that have been made of the Cairngorm framework. These included:
312
❑
The expense of data binding
❑
Overreliance on singleton classes
❑
The large number of classes
Chapter 29: Looking Back and Ahead Chapter 26 examined some ideas concerning best practices when using the Cairngorm framework, put forth by Jesse Warden (http://jessewarden.com/2007/08/10-tips-for-working-withcairngorm.html) and Eric Garza and Peter Martin of Adobe Consulting (https://share.acrobat .com/adc/adc.do?docid=e2120738-1a70-40ff-b5d3-c5231346b466). Examples of best practices included avoiding the use of the ViewHelper and ViewLocator classes and storing only strongly typed data classes, not simple properties, on the ModelLocator.
Tools and Add-Ons In Chapter 27 you were walked through the installation and use of the Cairngorm plug-in. This plug-in automates some of the more repetitive processes involved in creating Cairngorm classes, including the processes of creating controller classes and of registering event and command classes in the FrontController. Chapter 28 introduced you to some of the prevalent features of the Universal Mind Cairngorm Extensions. These included the features for using responders to notify calling classes of the results of remote calls, and the EventGenerator class, which can be used to trigger a sequence of events in series or parallel.
Online Resources This section lists some of the online resources that are available for learning more about the Cairngorm framework.
Adobe Open Source Website This site is the current home of the Cairngorm framework (http://opensource.adobe.com/ wiki/display/cairngorm/Cairngorm). This is the place to look for updates or a new version of Cairngorm. Additionally, you can obtain framework source code, submit bugs, and access the developer documentation.
cairngormdocs.org This is a community-led site devoted to providing resources for learning the Cairngorm framework (http://www.cairngormdocs.org/). It contains links to a variety of articles and the online documentation for the Cairngorm source code. It also contains a collection of sample applications that can be downloaded (http://cairngormdocs. org/blog/?cat=6).
313
Chapter 29: Looking Back and Ahead Examples of the types of application that you can download include: ❑
Indiver Nagpal’s ColdFusion-based “Contact Manager” example
❑
Chen Bekor ’s “CairngormStore with Stub BusinessDelegates” example
❑
Derek Wischusen’s Ruby on Rails and WebORB “Issue Tracker” example
At the time of the publication of this book, the most recent application was posted in 2007. The remaining applications on the first page (presumably the next most recent) were from 2006, and may represent an older version of the Cairngorm framework. However, they can still give you a sense of how applications are built with the Cairngorm framework.
Adobe Cairngorm Forum This is the official forum for the Cairngorm framework (http://forums.adobe.com/community/ opensource/cairngorm). This is where you can go to see what topics are being discussed in the Cairngorm developer community. The forums are broken into the following areas of discussion: ❑
General
❑
Flex Builder plug-in
❑
Commits
❑
Cairngorm development
Steven Webster’s and Alistair McLeod’s Blogs The original creators of Cairngorm, Steven Webster (http://blogs.adobe.com/swebster/) and Alistair McLeod (http://blogs.adobe.com/amcleod/), both have blogs on adobe.com. The most recent dated posts on both blogs were from August 2008, but there are enough posts related to Cairngorm that you can get a sense of how the framework developed and some ideas about how it can be used.
Adobe Developer Connection This site is not Cairngorm-specific, but contains links to several articles and resources related to Cairngorm, including the “Flex 3: Introducing Cairngorm” and “Developing Flex RIAs with Cairngorm microarchitecture” articles referenced in this book. (http://www.adobe.com/devnet/flex/ architecture.html)
Where To Go from Here I want to close this book by repeating a quote from Jesse Warden’s blog post “10 Tips for Working with Cairngorm” (http://jessewarden.com/2007/08/10-tips-for-working-with-cairngorm.html):
314
Chapter 29: Looking Back and Ahead I felt “comfortable with Cairngorm” on my fourth project with it. To this day, I still mod it though, and try different things. As more people come into Flex, there are more cool ideas and techniques floating around.
While this book has come to an end, the process of honing your skills with Cairngorm is just beginning. While I hope you have found this book an informative and valuable resource that you will turn to in the future, in learning any new topic there is no substitute for practice and experimentation. Given the variations in things like package structures in sample applications, and the often contradictory suggestions on best practices, it is not unreasonable if some things about the Cairngorm framework are still unclear to you (there are things that still seem unclear to me, even after writing this book). I would honestly encourage you, if you have not done so already, to download the FStop application (http://download.macromedia.com/pub/developer/f3ic_studentFiles_16Jun08.zip); the Cairngorm store application (http://www.cairngormdocs.org/exampleApps/Cairngorm StoreWeb2_1.zip); and any of the examples at cairngormdocs.org (http://cairngormdocs.org/ blog/?cat=6), and to spend some time simply going through them to see how things are done in those examples. Similarly, take some time to go through the blogging application you built in this book and try things like adding new features to it or revising existing ones. Finally, Cairngorm is an open source project. If you find that there is something about the framework that is not working for you, or you simply have ideas about how it can be improved, the source code is there for you to modify and play with. Get involved in the forums and discuss ideas for improvements. It is only with the involvement of the community that an open source project can evolve and improve.
315
Index
Index A AbstractServices, 4, 30, 32-33 accessLevel, 108, 131, 169 Action Message Format (AMF), 89, 95 ActionScript abstract classes and, 4 “ActionScript 2.0 design patterns for rich Internet applications” section, 2 Advanced ActionScript 3 with Design Patterns, 18 event listener model and, 21 Java v., 11 New ActionScript Class dialog box, 58, 59, 66, 76 object-oriented programming concepts and, xxiii private constructors and, 23, 36, 125 RIAs and, 2 singleton classes and, 23 adapters, 16 Add Cairngorm Nature dialog, 287-288 Add Cairngorm Project Nature, 287 add posts feature (blogging application), 159-172 commands and, 164-166 debug, 169, 170 delegates and, 162-164 events and, 161-162 implementation, 169-171 overview/requirements, 108, 159-160 prescribed order for creating classes, 129-130, 218 revising, 233-237 testing, 169-171 value objects and, 160-161 views and, 167-168 addCommand function, 57, 60, 61, 78 addComment function, 189, 191, 199, 240 AddCommentCommand, 191, 199, 240, 259 AddCommentDelegate, 189, 191, 199, 227, 240 AddCommentEvent, 187-188, 191, 193, 199, 223, 240 addPost method, 164, 166, 172, 233 AddPostCommand, 172, 233 AddPostDelegate, 163, 164, 166, 172, 227, 233 AddPostEvent, 162, 166, 168, 172, 223, 233 addProductToShoppingCart function, 95 AddProductToShoppingCartCommand, 94 Adobe Customer Training/Partner Enablement Group, 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 314 Adobe Developer Connection (http://www.adobe.com/ devnet/flex/architecture.html), 314
articles/resources, 314 Developing Flex RIAs with Cairngorm Micro-Architecture article, 13, 14, 267, 269, 314 Flex 3: Introducing Cairngorm article, 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 314 Adobe Integrated Runtime. See AIR Adobe Labs website, 1 Adobe Live Docs, 264 Adobe MAX (2008), 271, 276 Advanced ActionScript 3 with Design Patterns (Lott & Patterson), 18 AIR (Adobe Integrated Runtime), 111 applications, 111-112 database (blogging application) and, 111-112, 121 Flex Builder and, 112 SQLite and, 111, 112, 119 Alexander, Christopher, 15, 16. See also design patterns Alistair McLeod/Steven Webster blogs, 314 application frameworks, 13, 311 flexibility of, 248 goal of, 268, 272 purpose of, 218 application logic. See logic flow ApplicationControlBar, 126, 142 ApplicationInitializeCommand, 176-177, 181, 184 ApplicationInitializeEvent, 174-175, 180, 183, 223, 225, 227 “Architectural Atrocities, part 9: Cairngorm’s Model Locator pattern,” 267 architectural frameworks, 14, 311 definition of, 14 goal of, 268, 272 ArrayCollection, 47, 164, 165, 168, 178, 190 asynchronous calls, 251, 303, 307 AsyncToken, 83 authorId, 160 authorName, 160
B backend system (blogging application), 119-122, 312. See also blogging application database, 106 AIR and, 111-112, 121 categories table, 106, 160, 161 classes for, 119-121 comments table, 106, 186
backend system (blogging application) (Continued) backend system (blogging application) (Continued) FlexBlog.db file, 119 on local machine, 111-112, 121 posts table, 106, 160 posts_view table, 106 users table, 106, 131, 170 delegates and, 120, 121, 122 FlexBlogDatabaseManager and, 119, 120. See also FlexBlogDatabaseManager SQLiteManager and, 119, 120 Bates, Bert, 17 Beck, Kent, 15 behavioral patterns, 16 best practices, 271-278, 313 commands, 272-275, 276, 277 conflicting suggestions for, 277-278 delegates, 273-275 events, 274-275 FrontController, 277 ModelLocator, 276-277 mx.rpc.Responder, 275. See also mx.rpc.Responder “RIA Development with Cairngorm: Tips from the Experts,” 271, 276-277 SequenceCommand, 276 team agreement on actions, 278 “10 Tips for Working with Cairngorm,” 271-275, 314 ViewLocator/ViewHelper, 275. See also ViewHelper; ViewLocator views, 275 Bindable metadata tag, 264 blogging application (sample project) add posts feature, 159-172 backend system, 119-122, 312 classes (total amounts) in, 220-221, 268 client, 105 commands in, 219-220, 228 commenting system, 185-200 database, 106 AIR and, 111-112, 121 categories table, 106, 160, 161 classes for, 119-121 comments table, 106, 186 FlexBlog.db file, 119 on local machine, 111-112, 121 posts table, 106, 160 posts_view table, 106 users table, 106, 131, 170 delegates types in project, 227-228 usage in project, 219-220 events types in project, 223-224 usage in project, 219-220 events/commands/delegates (usage) in, 219-220 experimenting with, 315 features list, 217, 312 order for class creation, 129-130, 218
318
Flex archive project, 111-118 load posts feature, 173-184 login/logout system, 129-130, 147-157 main application, 106, 123-127 Miss Information Media and, 105, 106, 107, 217 overview, 105-109, 312 revisions add posts feature, 233-237 commenting system, 240-244, 260-261 load posts feature, 237-239 login/logout system, 231-233 search system, 244-248 user registration feature, 228-230, 254-257 search system. See search system sections of, 106 tables for, 106 user registration, 107, 129-145 value objects (usage) in, 219 version one (review), 217-222 version two, 220-258 calling methods on views, 249-258, 312 combining classes, 220-222, 233-248 consolidated version, 248 views (usage) in, 220 blogs Alistair McLeod, 314 Steven Webster, 314 body (column), 160, 186 Boles, Matt, 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 314 breaking down views, 180-182, 184, 192-194, 200, 220 Builder (creational pattern), 16. See also Flex Builder Burleson, Thomas, 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 251, 252, 303, 314
C CafeTownsend_CGX sample project, 303, 305 Cairngorm benefits of, 10-11, 310 best practices, 271-278, 313 “RIA Development with Cairngorm: Tips from the Experts,” 271, 276-277 “10 Tips for Working with Cairngorm,” 271-275, 314 class structure outline, 3 commands. See commands complicated nature of, 272 components of, 310, 311-312 creators of, 314. See also McLeod, Alistair; Webster, Steven criticisms, 220, 263-269, 312 classes, 220, 268, 272 data binding, 264, 268 singleton design pattern, 265-268, 312 criticisms of, 220, 263-269, 312 debugging and, 11 descriptions/definitions of, 1-2 design patterns. See design patterns
documentation, 3 enterprise version, 115 events. See events Extensions. See Cairngorm Extensions Flex 3: Introducing Cairngorm article, 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 314 future plans for, 3 history of, 2-3 improvements for, 315 introduction, 1-18, 309-310 J2EE and, 2, 3, 11, 309 logic flow of, 97-104, 310 micro-architecture, 1-2 MVC and, 1, 17, 19-21. See also Model View Controller as open source project, 3, 315 overview, 3-10, 309-310 packages. See packages packages in, 3-6, 309-310 parallel working and, 1, 218 plug-in. See Cairngorm Eclipse plug-in project organization, 9-10, 310 resources (online), 313-314 RIAs and. See RIAs standard version, 115 team development and, 2, 11, 14, 219, 268, 272, 278, 310 value objects. See value objects “Why I Think You Shouldn’t Use Cairngorm,” 269 Cairngorm Eclipse plug-in, 279-300 Cairngorm locations (configuration), 285-287 Cairngorm Project Nature and, 287-288 Cairngorm SWC (beta version) and, 279, 287, 300 command creation, 296-298 configuration, 293-295 controller creation, 289-293, 299 documentation, 279 Eclipse update manager, 280-285 Flex Builder Preferences dialog, 285-287 forum for, 314 FrontController and, 289-293 installation, 279-285 issues with, 279, 289, 299 Properties for CairngormPlugin dialog, 293-294, 295 Cairngorm Extensions, 301-307, 313 Burleson on, 303 commands and, 302 delegates and, 302 documentation on, 303, 307 EventGenerator and, 306-307 events and, 301 features of, 301-302 FrontControllers and, 302 Google Code site and, 301, 302 notifying callers and, 303-306 remote service calls and, 303-306 ServiceLocator and, 302 views and, 301-302 Cairngorm Project Nature, 287-288
“Cairngorm Secret Tip #3: Responders for View Notifications,” 251 Cairngorm Store application. See Store application Cairngorm SWC (beta version), 115-117, 279, 287, 300 cairngormdocs.org cairngormdocs.org/docs/cairngorm_2_2_1/com/adobe/ cairngorm/business/package-detail.html, 30 cairngormdocs.org/exampleApps/CairngormStoreWeb .zip, 47 examples/samples, 9, 313-314, 315 Store application, 9, 315 ViewLocator class, 250 CairngormError, 4 CairngormEvent, 5, 69 in Cairngorm logic flow, 8, 20, 97, 98 purpose of, 63, 70 CairngormEventDispatcher, 5, 8, 63, 64-65, 70, 71, 75, 272, 311 CairngormLoginModel, 104 CairngormMessageCodes, 4 Cairngorm.swc file, 116, 117 Callbacks class, 304-305, 307 calling methods on views, 249-258, 312. See also blogging application alternative methods, 250, 257 iResponder interface/Responder class and, 251-254 problem, 249-250 ViewHelper/ViewLocator and, 5, 222, 250-251, 258, 275 catch blocks, 121, 134 categories table, 106, 160, 161 category searches, 109, 201, 202, 203, 204, 205, 207, 214, 215, 225, 248. See also search system CategoryDelegate, 228, 233, 235 CategoryEvent, 225, 227, 233, 235, 236 categoryId, 160, 161 categoryName, 160 CategorySearchCommand, 207-208, 215, 244 CategorySearchDelegate, 204-205, 215, 227, 244 CategorySearchEvent, 203, 215, 223, 244 CategorySearchView, 209-210, 216 CategoryVO, 161, 163, 172, 202, 215 ChangeMainViewCommand, 135, 145 ChangeMainViewEvent, 132, 133, 141, 142, 145, 157, 180, 223, 225, 227 Checkout, 52 class templates, 279, 285, 299 classes (Cairngorn). See also singleton classes; specific classes class structure outline, 3 combining, 221-222, 233-248 criticism of, 220, 268, 272 prescribed order for creating classes, 129-130, 218 reducing number of, 275 total amounts, in blogging application, 220-221, 268 Clear button, 145, 213, 214, 248 clearMessage function, 138 clearSearchResultsButton, 247 ClearSearchResultsCommand, 215, 244
319
Index
ClearSearchResultsCommand
ClearSearchResultsEvent ClearSearchResultsEvent, 203, 208, 210, 215, 224, 244 client (command pattern element), 24, 25 code generator, class templates and, 279, 285, 299 code separation, by roles/responsibilities, 10, 11, 19, 273 com.adobe.cairngorm, 4 com.adobe.cairngorm.business, 4, 30, 36 com.adobe.cairngorm.commands, 4 com.adobe.cairngorm.control events and, 63, 66 FrontController and, 5, 56, 57, 59 com.adobe.cairngorm.control.CairngormEvent class, 66, 70 com.adobe.cairngorm.control.FrontController class, 57, 62 com.adobe.cairngorm.model, 5, 46, 310 com.adobe.cairngorm.view, 5 com.adobe.cairngorm.vo, 5, 6, 90 combining classes, 221-222, 233-248 commands and, 228 delegates, 222, 227-228, 248 events, 221, 223-227, 248 combo box, 160, 168, 170, 171, 172, 236 Command (Cairngorm Command), 279, 289, 290, 296 command calls, sequencing, 259-262, 312. See also SequenceCommand class Command interface, 4, 24, 74, 80. See also ICommand command pattern, 16, 23-25 command prefix, 76, 80 Command.asjet, 299 commands (Cairngorm command classes), 7, 73-80 add posts feature and, 164-166 best practices, 272-275, 276, 277 in blogging application, 219-220, 228 Cairngorm Extensions and, 302 in Cairngorm logic flow, 8, 20, 99-100 characteristics of, 73-74, 80 combining, 228 commenting system and, 189-192 creating, 76-78 Cairngorm plug-in and, 296-298 description of, 74-76 event-command pairings, 7, 25, 57, 60, 61, 62, 129 /events/delegates, one-to-one relationship and, 68, 71, 218, 220, 221, 268, 274, 278, 312 load posts feature and, 176-179 login/logout system and, 150-152 naming convention for, 76, 80 and parsing data, 273-274 purpose of, 7, 311 search system and, 205-209 user registration feature and, 129, 135-137 using, 78-80 in blogging application, 219-220, 228 three ways to use, 274-275 CommentDelegate, 228, 240, 241, 242 CommentDisplay, 193, 194, 199, 200 CommentEvent, 227, 240, 242, 243, 259, 261 CommentForm, 192-193, 194, 196, 200 commentId, 186 commenting system (blogging application), 185-200
320
commands and, 189-192 debug, 195, 196, 261 delegates and, 188-189 events and, 187-188 implementation, 195-198 overview/requirements, 108-109, 185-186 prescribed order for creating classes, 129-130, 218 revising, 240-244, 260-261 SequenceCommand and, 260-261 testing, 195-18 value objects and, 186-187 views and, 192-194 CommentList, 194, 199, 200 comments table, 106, 186 commentSubmissionStatus property, 191, 193, 194, 199 commentSubmitted setter function, 193, 194 CommentView, 194, 200, 211 CommentVO, 186-187, 193, 194, 199 complicated nature, of Cairngorm, 272 composites, 16 concrete command, 24 consequences (design pattern element), 15 consolidating classes. See combining classes Controller (Cairngorm Controller), 279, 289, 290 controller prefix, 58 Controller.asjet, 299 controllers creating, Cairngorm plug-in and, 289-293, 299 MVC and, 17, 18, 19 /views, one-to-one relationship of, 18, 20, 27 Core J2EE Pattern Catalog, 2 createdOn, 160 creational patterns, 16 creationComplete, 169, 172 criticisms (of Cairngorm), 220, 263-269, 312 classes, 220, 268, 272 data binding, 264, 268 singleton design pattern, 265-268, 312 CSS style, 138 Cunningham, Ward, 15 currentPost property, 180, 183, 184, 193, 195, 199, 200 currentUser property, 154, 155, 157, 167, 193, 200 Customize Perspective dialog, 289, 290
D data binding, 7, 8, 19, 20, 21, 45, 51, 53, 97. See also ModelLocator criticism of, 264, 268 online description, 264 data property, 69, 71, 226, 241, 242, 245, 246, 248 data transfer objects (DTOs), 89, 94, 95, 121, 219. See also value objects database (blogging application), 106 AIR and, 111-112, 121 classes for, 119-121. See also FlexBlogDatabaseManager; SQLiteManager
FlexBlog.db file, 119 on local machine, 111-112, 121 tables categories, 106, 160, 161 comments, 106, 186 posts, 106, 160 posts_view, 106 users, 106, 131, 170 dataProvider property, 161, 172, 179, 181, 184, 194, 210, 216 www.davidtucker.net, 273 debugging. See also testing add posts feature, 169, 170 Cairngorm and, 11 commenting system, 195, 196, 261 load posts feature, 182, 183 login/logout system, 155 main application, 127 search system, 211, 212 user registration feature, 143 delegate prefix, 84, 87 delegates (delegate classes), 7, 81-87 add posts feature and, 162-164 backend system (blogging application) and, 120, 121, 122 best practices, 273-275 in blogging application types in project, 227-228 usage in project, 219-220 Cairngorm Extensions and, 302 in Cairngorm logic flow, 8, 20, 97, 101-102 characteristics of, 81-82, 86-87 combining, 222, 227-228, 248 /commands/events, one-to-one relationship and, 68, 71, 218, 220, 221, 268, 274, 278, 312 commenting system and, 188-189 creation of, 83-85 description of, 82-83 load posts feature and, 175-176 location of, 83, 84, 87 login/logout system and, 149-150 naming convention for, 84, 87 parsing data and, 273-274 purpose of, 7, 312 search system and, 203-205 user registration feature and, 129, 134-135 using, 85-86 in blogging application, 219-220 three ways to use, 274-275 deprecated items Command interface, 4, 24, 74, 80. See also ICommand getInvokerService, 36. See also getHTTPService; getRemoteObject; getWebService getService, 36 ModelLocator interface, 5, 46, 48. See also IModelLocator Responder interface, 4, 30, 31, 75, 258. See also mx.rpc.IResponder ValueObject interface, 5, 90. See also IValueObject ViewHelper, 5, 250, 251, 258, 275
ViewLocator, 5, 250, 251, 258, 275 design patterns, 15-27, 311. See also Model View Controller Alexander on, 16 appropriateness of, 269 behavioral, 16 Cairngorm, 14, 17, 26 categories of, 16 creational, 16 defined, 15-16, 311 elements of, 15 GOF book and, 15, 16, 17, 21, 22, 23, 25 iterator, 16 micro-architecture and, 14 monostate pattern and, 266-267 MVC v., 17 observer (publish/subscribe), 16, 21 singleton, 16, 22-23 criticism of, 265-268, 312 defined, 22 structural, 16 Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, and Vlissides), 15 Developing Flex RIAs with Cairngorm Micro-Architecture article (Webster & Tanner), 13, 14, 267, 269, 314 Developing Rich Clients with Macromedia Flex (Webster & McLeod), 3 Dictionary object, 41, 57 documentation (Cairngorm), 3 doLogin function, 153, 232 doRegistration function, 141, 230, 256 DTOs (data transfer objects), 89, 94, 95, 121, 219. See also value objects
E Eclipse Modeling Framework(EMF), 299 Eclipse plug-in. See Cairngorm Eclipse plug-in Eclipse update manager, 280-285 EMF (Eclipse Modeling Framework), 299 enterprise version, of Cairngorm, 115 Europa Discovery Site, 282 event prefix, 66, 71 Event.asjet, 299 event-command pairings, 7, 25, 57, 60, 61, 62, 129 eventDispatcher, 65 EventGenerator, 306-307 MXML and, 306-307 parallel events and, 306, 307, 313 SequenceCommand v., 306 events (Cairngorm event classes), 7, 63-71 add posts feature and, 161-162 best practices, 274-275 Cairngorm Extensions and, 301 com.adobe.cairngorm.control and, 63, 66 com.adobe.cairngorm.control.CairngormEvent class and, 66, 70
321
Index
events (Cairngorm event classes)
events (Cairngorm event classes) (Continued) events (Cairngorm event classes) (Continued) combining, 221, 223-227, 248 /commands/delegates, one-to-one relationship and, 68, 71, 218, 220, 221, 268, 274, 278, 312 commenting system and, 187-188 creating, 66-69 description of, 63-65 flash.events.Event class and, 63, 64, 70 Flex events and, 61, 63, 70, 221, 275 FStop application and, 66, 70 load posts feature and, 174-175 locations of, 66, 70 login/logout system and, 148-149 naming convention for, 66 parallel, EventGenerator and, 306, 307, 313 purpose of, 7, 63, 311 registering, 69 search system and, 202-203 Store application and, 66, 70 types, for blogging application, 223-224 user registration feature and, 129, 132-133 using, 69-70, 71 in blogging application, 219-220 three ways to use, 274-275 in views, 69, 275 examples/samples. See blogging application; CafeTownsend_CGX sample project; cairngormdocs. org; login form execute method, 24, 25 executeCommand, 57 executeNextCommand, 75, 259, 260, 261 experimentation, practice and, 315
F factory method, 16, 265, 271 Fain, Yakov, 263 fault(info:Object):void, 75 firstName (column), 131 flash.events.Event class, 63, 64, 70 flash.net.Responder, 272, 275. See also mx.rpc.Responder Flex 3: Introducing Cairngorm article (Adobe Customer Training/Partner Enablement Group, Burleson, Shuman, and Boles), 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 314 Flex archive project, 111-118. See also blogging application Flex Builder AIR and, 112 automatic code-hinting feature, 218 automatic importing feature, 218 Code Model API, 299 Eclipse and, 279. See also Cairngorm Eclipse plug-in importing projects into, 112-114 libs folder and, 116 Preferences dialog, 285-287
322
Flex events, 61, 63, 70, 221, 275. See also events Flex mx.rpc.IResponder, 4, 31, 75, 80, 222, 303-304 Flex RDS, 29, 302 Flex Store, 302. See also Store application Flex validation classes, 130, 140, 160, 186 FlexBlogController, 123, 135, 166, 179 FlexBlogDatabaseManager, 119, 120, 121, 122, 134 backend and, 119, 120 updated, 130, 147, 159, 173, 185, 201 FlexBlog.db file, 119. See also database FlexBlogModel, 124, 135, 136, 164, 165 frameworks, 13-14, 311 application, 13, 218, 248, 268, 311 appropriateness of, 269 architectural, 14, 268, 272, 311 EMF, 299 micro-architectures v., 2, 14 types of, 13-14 Freeman, Eric, 17 FrontController, 7, 55-62 best practices, 277 Cairngorm Eclipse plug-in and, 289-293 Cairngorm Extensions and, 302 in Cairngorm logic flow, 8, 20, 97, 99 characteristics of, 55, 62 code for, 56-57 com.adobe.cairngorm.control and, 5, 56, 57, 59 com.adobe.cairngorm.control.FrontController class and, 57, 62 creation of, 57-60 event-command pairings and, 7, 25, 57, 60, 61, 62, 129 FStop application and, 57, 58, 62 Garza/Martin suggestions for, 277 location of, 57, 58, 62 for main application, 123-124, 125, 127 naming convention for, 58 purpose of, 5, 7, 311 Store application and, 58, 62 using, 60-62 FStop application, 10. See also packages download, 315 events and, 66, 70 FrontController and, 57, 58, 62 ModelLocator and, 49 package structure, 9, 10, 114, 310 ServiceLocator and, 41
G Gamma, Erich, 15. See also GOF book gang of four book. See GOF book Garza, Eric, 271, 276, 277, 278, 313 getCategories, 160, 163, 172 getCommand, 57 getComments function, 189, 199 getHTTPService, 31, 36, 44
getInstance(), 22 getInvokerService, 36. See also getHTTPService; getRemoteObject; getWebService getPostsByCategory function, 205, 215 GetProductsCommand, 79, 85 getRecentPosts method, 176 getRemoteObject, 31, 36, 44 getService, 36 getWebService, 31, 36, 44 global variables, singletons v., 266 GOF (gang of four) book, 15, 16, 17, 21, 22, 23, 25. See also Design Patterns: Elements of Reusable ObjectOriented Software Google Code site, 301, 302 GraphicalProductList, 52
H HBox tags, 126 Head First Design Patterns (Freeman, Robson,Sierra, and Bates), 17 “Hello World,” 268 Helm, Richard, 15. See also GOF book HTTPService, 7, 26, 219 code for, 36-37 Hultberg, Theo, 267
I ICommand, 4, 74, 80 IModelLocator, 5, 46, 53, 90 implementsICommand, 57 improvements, for Cairngorm, 315 includeInLayout property, 210 init function of creationComplete, 169, 172 of MainView, 180 initializeCommands, 61 invoker, 24, 25 IResponder interface, 4, 31, 75, 80, 222, 251-254, 303-304 IServiceLocator, 4, 30-31 IServices, 4, 30, 31 itemClass property, 121-122 itemClick, 179, 210, 247 itemRenderer, 193, 194, 199, 200 iterator pattern, 16 IValueObject interface, 5, 90, 94
J J2EE Cairngorm and, 2, 3, 11, 309 Pattern Catalog, 2
RIA development, 3, 309 Java Cairngorm and, 11 J2EE Pattern Catalog, 2 J2EE RIA development, 3, 309 Java class, 91 http://java.sun.com/blueprints/corej2eepatterns/ Patterns/index.html, 2 Reality J2EE — Architecting for Flash MX, 2 RIA development, 3, 309 Java Emitter Templates (JET), 299 Java Server Pages (JSP), 299 http://jessewarden.com/2007/08/10-tips-for-workingwithcairngorm.html, 313, 314 JET (Java Emitter Templates), 299 JET Tutorial Part 1/Part 2, 299 Johnson, Ralph, 15. See also GOF book JSP (Java Server Pages), 299
K keyword searches, 109, 201, 202, 204, 205, 206, 211, 214, 225, 247. See also search system keywords (private variable), 206 keyWordSearch function, 204, 215 KeyWordSearchCommand, 206, 208, 215, 244 KeyWordSearchDelegate, 204, 215, 227, 244 KeyWordSearchEvent, 202, 207, 209, 215, 224, 244
L labelField property, 179 lastName (column), 131 Library path feature, 116 libs folder, 116 LiveCycle Data Services, 115 LOAD, 259 load posts feature (blogging application), 173-184. See also blogging application commands and, 176-179 delegates and, 175-176 events and, 174-175 implementation, 180-183 overview/requirements, 108, 173-174 prescribed order for creating classes, 129-130, 218 revising, 237-239 testing, 180-183 value objects and, 174 views and, 179-180 loadCategories method, 165, 233 LoadCategoriesCommand, 164, 172, 233 LoadCategoriesDelegate, 162, 163, 165, 172, 227, 233 LoadCategoriesEvent, 162, 172,223, 233 loadComments function, 189, 190, 199, 240 LoadCommentsCommand, 190, 199, 240
323
Index
LoadCommentsCommand
LoadCommentsDelegate LoadCommentsDelegate, 188-189, 190, 199, 227, 240 LoadCommentsEvent, 187, 190, 191, 195, 199, 224, 240 loadPosts method, 121 loadRecentPosts method, 176, 178, 237 LoadRecentPostsCommand, 177-178, 184, 195, 199, 237 LoadRecentPostsDelegate, 176, 178, 183, 227, 237 LoadRecentPostsEvent, 175, 183, 224, 237 locator prefix, 49, 53 loggedin setter function, 154, 155 logic flow (Cairngorm), 7-9, 310 diagrams, 8, 20, 97 login form (sample project), 97-104 command decides how to handle event, 99-101, 104 command updates ModelLocator, 102, 104 delegate calls remote service, 101-102, 104 FrontController intercepts event and triggers command, 99, 104 ModelLocator notifies views of changes via data binding, 103, 104 view dispatches event, 98, 104 MVC and, 20-21 summary, 310 logic flow (MVC), 18 Login component, 152 login form (sample project), 97-104, 312 Cairgorm logic flow command decides how to handle event, 99-101, 104 command updates ModelLocator, 102, 104 delegate calls remote service, 101-102, 104 FrontController intercepts event and triggers command, 99, 104 ModelLocator notifies views of changes via data binding, 103, 104 view dispatches event, 98, 104 LoginCommand, 99, 100, 104, 149, 150, 231, 298 LoginControl, 104 LoginDelegate, 100, 101, 104, 150, 151, 156, 227, 231 LoginEvent, 98, 101, 104, 148, 149, 153, 156, 157, 224, 231, 297 LoginForm view, 104 login/logout system (blogging application), 107-108, 147-157 commands and, 150-152 debug, 155 delegates and, 149-150 events and, 148-149 implementation, 154-156 overview/requirements, 107-108, 147-148 prescribed order for creating classes, 129-130, 218 revising, 231-233 testing, 154-156 value objects and, 148 views and, 152-154 LogoutCommand, 152, 156, 231 LogoutEvent, 149, 156, 157, 224, 231 logout/login system. See login/logout system Lord, Richard, 266 Lott, Joey, 18
324
M Macromedia MAX2004 conference, 3 madeOn, 186 main application (blogging application). See also blogging application debug, 127 FrontController for, 123-124, 125, 127 ModelLocator and, 124-125, 127 overview, 106 setup, 125-127 MainView component, 126, 127, 135, 141, 142 init function of, 180 reorganization of, 180-181 Martin, Peter, 271, 276, 277, 278, 313 McLeod, Alistair, 2, 3, 314. See also Webster, Steven ActionScript 2.0 Dictionary, 2 http://blogs.adobe.com/amcleod, 314 Developing Rich Clients with Macromedia Flex, 3 Reality J2EE — Architecting for Flash MX, 2 /Webster, Cairngorm founders, 2, 314 message (private variable), 138 messageStyle (private variable), 138 metadata tag Bindable, 264 RemoteClass, 89, 90, 95 micro-architectures. See also design patterns Cairngorm, 1-2 defined, 14, 311 design patterns and, 14 Developing Flex RIAs with Cairngorm Micro-Architecture article, 13, 267, 269, 314 frameworks v., 2, 14 Miss Information Media, 105, 106, 107, 217. See also blogging application Model (in MVC), 17, 18, 19 model prefix, 49, 53 Model View Controller (MVC), 17-21. See also controllers; views Advanced ActionScript 3 with Design Patterns and, 18 Cairngorm and, 1, 17, 19-21 controllers in, 17, 18, 19 design patterns v., 17 Head First Design Patterns and, 17 logic flow diagram, 18 Model in, 17, 18, 19 PureMVC and, 267, 268 reuse and, 19 View in, 17, 18, 19, 20 ModelLocator, 7, 45-53 “Architectural Atrocities, part 9: Cairngorm’s Model Locator pattern,” 267 best practices, 276-277 in Cairngorm logic flow, 8, 20, 21, 97, 102-103 characteristics of, 45-46, 53 com.adobe.cairngorm.model and, 5, 46, 310 creation of, 49-50, 53 data binding and, 7, 8, 19, 20, 21, 45, 51, 53, 97
FStop application and, 49 Garza/Martin suggestions for, 276-277 interfaces in, 46 main application file and, 124-125, 127 naming convention, 49, 53 purpose of, 5, 7, 311 Store application and, 47, 49, 267 using, 50-52 ModelLocator interface, 5, 46, 48. See also IModelLocator monostate pattern, 266-267 MouseEvent class, 21, 223 MVC. See Model View Controller mx.collections classes, 50, 53 MXML EventGenerator and, 306-307 main application and. See main application MXML views, 10, 115, 310 ServiceLocator and, 41, 42, 44 mx.rpc.IResponder, 4, 31, 75, 80, 222, 303-304 mx.rpc.Responder, 222, 272, 275, 304, 305
N name (column), 161 namespaces, 125 naming convention for commands, 76, 80 for delegates, 84, 87 for events, 66, 71 for FrontController, 58 for ModelLocator, 49, 53 prefix command, 76, 80 controller, 58 delegate, 84, 87 event, 66, 71 locator, 49, 53 model, 49, 53 VO, 94, 95 for value objects, 94, 95 New ActionScript Class dialog box, 58, 59, 66, 76 New Cairngorm Controller dialog, 290, 291 nextEvent, 75, 259, 260 NotificationDisplay, 138, 142 notifications, 131, 132 notifying callers, Cairngorm Extensions and, 303-306 notifying views of status of procedure calls. See calling methods on views
O Object Oriented Programming, Systems, Languages and Applications (OOPSLA), 15 object-oriented programming. See also ActionScript; Java ActionScript and, xxiii
Design Patterns: Elements of Reusable Object-Oriented Software, 15 observer pattern (publish/subscribe pattern), 16, 21 one-to-many relationship, 16, 21 one-to-one relationship events/commands/delegates, 68, 71, 218, 220, 221, 268, 274, 278, 312 views/controllers, 18, 20, 27 online resources (Cairngorm), 313-314 onPostSelected method, 179, 239 OOPSLA (Object Oriented Programming, Systems, Languages and Applications), 15 open source project, Cairngorm as, 3, 315 Open Type dialog box, 59, 67, 77, 294 organization, of projects. See package structures override keyword, 260
P Package Selection dialog, 291, 292 package structures (projects), 9-10. See also FStop application; Store application FStop v. Store application, 9, 10, 114, 310 recommended, 10, 114-115, 310 packages, in Cairngorm, 3-6, 309-310 parallel events, EventGenerator and, 306, 307, 313 parallel working, Cairngorm and, 1, 218 parsing data commands and, 273-274 delegates and, 273-274 parsing utility class, 273 Partner Enablement Group. See Adobe Customer Training/ Partner Enablement Group password (column), 131 pattern name, 15. See also design patterns Patterson, Danny, 18 plug-in. See Cairngorm Eclipse plug-in poe account, 155, 170, 183, 196, 233, 236, 243, 261 posts feature. See add posts feature; load posts feature PostDelegate, 222, 228, 233, 234, 236, 237, 238 PostDisplay view, 179-180, 181, 184 postedBy, 186 PostEditor view, 167, 169, 172, 182, 184, 236 PostEvent, 221, 224, 233, 234, 236, 237, 239 postId, 160, 186, 199 PostList view, 179, 181, 184, 210, 220 posts table, 106, 160 posts view, 130, 132, 141, 145, 156, 157, 159, 160, 181 postSubmissionStatus property, 166, 168, 169 postSubmitted setter function, 168, 169, 171 posts_view table, 106 PostView, 181, 184, 195, 211, 216 PostVO, 122, 161, 162, 172, 241, 242, 255, 273 practice, experimentation and, 315 prefix command, 76, 80 controller, 58
325
Index
prefix
prefix (Continued) prefix (Continued) delegate, 84, 87 event, 66, 71 locator, 49, 53 model, 49, 53 private constructors, 23, 36, 125 private static variables, 22, 27, 125, 267 private variables eventDispatcher, 65 keywords, 206 message, 138 messageStyle, 138 searchTitle property and, 208 problems (design pattern element), 15 ProductDelegate, 83, 84, 86 ProductsAndCheckoutViewStack component, 51-52 ProductVO, 90, 94, 95 project organization. See package structures Project Setup Wizard, 117 projects. See also blogging application; login form linking SWL files to, 115-118 Properties for CairngormPlugin dialog, 293-294, 295 proxies (proxy pattern), 16, 25-26 “pseudo-abstract” base class, 4, 74, 80 public static methods, 22, 36, 125, 265 publish/subscribe pattern. See observer pattern PureMVC, 267, 268
R RDS (Remote Development Services), 29, 302 receiver, 24, 25 Recent Posts list, 173, 183, 198, 244 registration feature. See user registration feature registering events, 69 RegisterUserCommand, 136, 137, 145, 228, 230, 255 RegisterUserDelegate, 134, 136, 137, 145, 227, 228, 230 RegisterUserEvent, 133, 137, 145, 224, 225, 228, 230 RegisterView, 181, 184, 256, 266 registrationComplete, 141, 143 RegistrationForm, 139-140, 142, 184 registrationStatus property, 141, 143, 255, 256 Remote Development Services (RDS), 29, 302 remote service calls, 303-306 Cairngorm Extensions and, 303-306 typical method, 303 RemoteClass metadata tag, 89, 90, 95 RemoteObjects, 36, 83 code for, 38-39 remoting capabilities, 2, 3, 309 remoting classes, 7, 26, 76, 219 removeCommand function, 57 resources (Cairngorm), 313-314 /Resources/code/ch12/FlexBlog.zip, 112, 113 /Resources/code/ch14/, 127 /Resources/code/ch15/, 145
326
/Resources/code/ch15/src/sql/, 130 /Resources/code/ch16/, 156 /Resources/code/ch16/src/sql/, 147 /Resources/code/ch17/src/sql/, 159 /Resources/code/ch18/, 183 /Resources/code/ch18/src/sql/, 173 /Resources/code/ch19/src/sql/, 185 /Resources/code/ch20/, 214 /Resources/code/ch20/src/sql/, 201 /Resources/code/ch22/, 248, 261 /resources/flexbuilder/cairngorm.xml, 299 /resources/flexbuilder/templates folder, 299 Responder class, 4, 30, 31, 75, 222, 251-254, 258, 272, 275, 304, 305 restructuring views. See breaking down views ResultEvent, 86, 102 result(data:Object):void, 75 reuse components, 275 global variables and, 266 MVC and, 19 PostList view, 210, 220 UserVO/UserNotificationVO, 156 views, 220, 268 revisions (blogging application) add posts feature, 233-237 commenting system, 240-244, 260-261 load posts feature, 237-239 login/logout system, 231-233 search system, 244-248 user registration feature, 228-230, 254-257 “RIA Development with Cairngorm: Tips from the Experts” (Garza & Martin), 271, 276-277 RIAs (rich Internet applications), 1 “ActionScript 2.0 design patterns for rich Internet applications,” 2 ActionScript and, 2 Cairngorm and, 1, 2, 309 Developing Flex RIAs with Cairngorm Micro-Architecture article, 13, 14, 267, 269, 314 HTTPService and, 7, 26 J2EE RIA development, 3, 309 rich Internet applications. See RIAs Robson, Elisabeth, 17 roles/responsibilities, code organization through, 10, 11, 19, 273. See also Model View Controller
S sample projects. See blogging application; CafeTownsend_ CGX sample project; cairngormdocs.org; login form SampleEvent, 252 search system (blogging application), 201-216. See also blogging application category searches, 109, 201, 202, 203, 204, 205, 207, 214, 215, 225, 248
commands and, 205-209 debug, 211-212 delegates and, 203-205 events and, 202-203 implementation, 211-214 keyword searches, 109, 201, 202, 204, 205, 206, 211, 214, 225, 247 overview/requirements, 109, 201-202 prescribed order for creating classes, 129-130, 218 revising, 244-248 value objects and, 202 views and, 209-210 SearchBoxView, 209, 216 SearchDelegate, 228, 244, 245, 246 SearchEvent, 227, 244, 246, 247 searchResults property, 207, 208, 210, 215, 216 SearchResultsView, 210, 214, 216 searchTitle property, 206, 207, 208, 210, 216 separation of code, by roles/responsibilities, 10, 11, 19, 273 SequenceCommand class, 259-262, 312. See also EventGenerator best practice, 276 code for, 74-75 commenting system and, 260-261 EventGenerator v., 306 Garza/Martin suggestion for, 276 “pseudo-abstract” base class, 4, 74, 80 purpose of, 4 sequencing command calls, 259-262, 312 ServiceLocator (Cairngorm component), 7, 29-44 Cairngorm Extensions and, 302 in Cairngorm logic flow, 8, 20, 97 characteristics, 29-30 classes in, 30 com.adobe.cairngorm.business and, 4, 30, 36 interfaces in, 30 purpose of, 4, 7, 311 ServiceLocator class code for, 33-36 creating, 41-42 using, 42-43 SetCurrentPostCommand, 178, 184, 199, 237 SetCurrentPostEvent, 175, 179, 183, 195, 210, 224, 237 ShopController class, 60 ShopModelLocator class, 47-48, 51 Shuman, Leo, 6, 9, 10, 11, 19, 29, 45, 55, 57, 66, 70, 73, 81, 83, 93, 314 Sierra, Kathy, 17 singleton classes. See also CairngormEventDispatcher; FrontController; ModelLocator; ServiceLocator; ViewLocator ActionScript and, 23 core, 27, 29, 45, 55 typical, 22 singleton pattern, 16, 22-23 criticism of, 265-268, 312
defined, 22 global variables v., 266 monostate pattern and, 266-267 overreliance on, 265, 268, 312 SingletonEnforcer class, 23, 125 “Singletons — we’re better off without them,” 266 solutions (design pattern element), 15 source code (Cairngorm), 3 SQL API, in synchronous mode, 121 SQL utility classes, 134 SQLite, 111, 112, 119 SQLiteManager, 119, 120 SQLStatement, 121 standard version, of Cairngorm, 115 static methods, 22, 36, 125, 265 static variables, 22, 27, 125, 267 Steven Webster/Alistair McLeod blogs, 314 Store application. See also packages Cairngorm Store application AddProductToShoppingCartCommand, 94 download, 9, 315 events and, 66, 70 FrontController and, 58, 62 ModelLocator and, 47, 49, 267 package structure, 9, 10, 114, 310 ProductDelegate, 83 ProductVO, 90 ServiceLocator and, 41 UpdateShoppingCartEvent, 94 value objects/vo package, 93 Flex Store application, 302 structural patterns, 16 submitComment function, 193, 243 submitCommentbutton, 193 submitPost function, 168, 169, 236 submitPostButton, 168 Subversion client, 3 SWC files, 115-117 beta version, 115-117, 279, 287, 300 CairngormExtensions.swc, 301, 302 Cairngorm.swc file, 116, 117 linking, to projects, 115-118 online information, 115 synchronous mode, SQL API in, 121
T tables. See also database categories, 106, 160, 161 comments, 106, 186 posts, 106, 160 posts_view, 106 users, 106, 131, 170 Tanner, 13, 14, 267, 269, 314 team development agreement on actions and, 278
327
Index
team development
team development (Continued) team development (Continued) Cairngorm and, 2, 11, 14, 219, 268, 272, 278, 310 templates class templates, 279, 285, 299 Command.asjet, 299 Controller.asjet, 299 Event.asjet, 299 JET, 299 solutions as, 15 “10 Tips for Working with Cairngorm” (Warden), 271-275, 314 testing add posts feature, 169-171 commenting system, 195-198 load posts feature, 180-183 login/logout system, 154-156 search system, 211-214 user registration feature, 143-145 TextualProductList, 52 tips. See best practices title (column), 160 triggerServiceCall function, 252 try-catch blocks, 121, 134 Tucker, David, 273
U UMEvent class, 305 Universal Mind, 6, 251, 301, 303, 307, 313. See also Flex 3: Introducing Cairngorm article Cairngorm Extensions, 301-307, 313 classes, 251 UpdateShoppingCartEvent, 94 user login/logout. See login/logout system user registration feature (blogging application), 107, 129145. See also blogging application commands and, 129, 135-137 debug, 143 delegates and, 129, 134-135 events and, 129, 132-133 implementation, 143-145 overview/requirements, 107, 130-131 prescribed order for creating classes, 129-130, 218 revising, 228-230, 254-257 testing, 143-145 value objects and, 129, 131-132 view and, 129, 137-143 UserCommand class, 306 UserDelegate, 82, 228, 229, 230, 231, 232, 255 UserEvent, 225, 227, 228, 231, 254, 256 userId (column), 131, 186 userName (column), 131 userNotification property, 136, 139 UserNotificationVO, 132, 137, 138, 145, 156, 190, 191 users table, 106, 131, 170 UserVO, 131, 133, 137, 145, 156 UUID constant, 292, 293, 298, 299
328
V validation classes, Flex, 130, 140, 160, 186 value objects (VOs), 7, 89-95 add posts feature and, 160-161 commenting system and, 186-187 creation of, 93-94 definition of, 89 description of, 90-93 as DTOs, 89, 94, 95, 121, 219 load posts feature and, 174 login/logout system and, 148 naming convention for, 94, 95 purpose of, 7, 312 RemoteClass metadata tag and, 89, 90, 95 search system and, 202 usage, in blogging application, 219 user registration feature and, 129, 131-132 using, 94-95 ValueObject interface, 5, 90. See also IValueObject versions, blogging application. See blogging application View (in MVC), 17, 18, 19, 20 ViewHelper, 5, 222, 250-251, 258, 275 ViewLocator, 5, 222, 250-251, 258, 275 views add posts feature and, 167-168 best practice, 275 breaking down, 180-182, 184, 192-194, 220 Cairngorm Extensions and, 301-302 in Cairngorm logic flow, 8, 20, 97 commenting system and, 192-194 /controllers, one-to-one relationship of, 18, 20, 27 events in, 69, 275 load posts feature and, 179-180 login/logout system and, 152-154 reuse, 220, 268 search system and, 209-210 usage, in blogging application, 220 user registration feature and, 129, 137-143 ViewStack, 126 visible property, 138, 154, 169, 193, 194 Vlissides, John, 15. See also GOF book VO prefix, 94, 95 VOs. See value objects
W Warden, Jesse, 271, 272, 273, 274, 275, 276, 277, 278, 313, 314 WebServices, code for, 39-40 Webster, Steven, 2, 3, 13, 14, 269, 314. See also McLeod, Alistair ActionScript 2.0 Dictionary, 2 http://blogs.adobe.com/swebster, 314
Developing Flex RIAs with Cairngorm Micro-Architecture article, 13, 14, 267, 269, 314 Developing Rich Clients with Macromedia Flex, 3 /McLeod, Cairngorm founders, 2, 314 Reality J2EE — Architecting for Flash MX, 2 http://weblogs.macromedia.com/swebster/ archives/2008/08/cairngorm_3_-_a.html, 3
“Why I Think You Shouldn’t Use Cairngorm” article, 269 “Why I Think You Shouldn’t Use Cairngorm,” 269 wrappers, 16 write view, 160, 169, 183, 236 WriteView, 182, 184
329
Index
WriteView
Now you can access more than 200 complete Wrox books online, wherever you happen to be! Every diagram, description, screen capture, and code sample is available with your subscription to the Wrox Reference Library. For answers when and where you need them, go to wrox.books24x7.com and subscribe today!
Related Wrox Books Adobe AIR: Create - Modify - Reuse ISBN: 978-0-470-18207-9 This book walks you through eleven fully implemented AIR applications with source code that you can use as they currently exist or customize. Each project begins with a discussion of architecture and design, followed by code implementation. You’ll get hands-on knowledge of AIR application design and development that you can then use to build dynamic RIAs.
Beginning Adobe AIR: Building Applications for the Adobe Integrated Runtime ISBN: 978-0-470-22904-0 This guide is a perfect mix of tutorials and hands-on coding, and provides the resources you need to build AIR applications quickly. Adobe expert Rich Tretola discusses the different programming languages and tools you can use for development and presents multiple methods for storing data, including within the file system and embedded database as well as storage on remote servers.
Professional Adobe Flex 3 ISBN: 978-0-470-22364-2 Understand the potential of the Flex 3 platform through practical examples and hands-on advice on topics like desktop deployment, developing applications in MXML, creating custom flex components, charting, targeting AIR, and data interconnectivity.
Professional BlazeDS: Creating Rich Internet Applications with Flex and Java ISBN: 978-0-470-46489-2 A hands-on guide to creating Rich Internet Applications (RIA) using BlazeDS. This informative resource provides you with detailed examples and walkthroughs that explain the best practices for creating RIAs using BlazeDS.
Create components and classes with the Cairngorm framework Although Cairngorm is the oldest and most widely used Flex framework, very little literature exists that clearly explains how to implement this framework. This book fills that void by showing you how Cairngorm facilitates team development, encourages best practices for building Flex-based RIAs, and works well with mediumto large-scale projects in which code needs to be separated and organized. •
Begins with an introduction to Cairngorm, focusing on the underlying principles, source code, and uses of Cairngorm
•
Examines the major components of Cairngorm including the ModelLocator, ServiceLocator, FrontController, Events, Commands, Delegates, and Value Objects
•
Presents a sample project that walks you through the various aspects of its organization and its handling of application logic
•
Explains how to make the most of events, commands, delegates, and value objects
•
Explores alternative methods for using events, commands, and delegates
•
Shares techniques for linking Cairngorm to a project
•
Offers suggestions for taking advantage of plug-ins and extensions
Jeremy Wischusen has worked for such clients as Dockers, Quicksilver, Major League Baseball, Sports Authority, CBS, and Liz Claiborne. He works with front- and backend systems, designing many projects from start to finish using such technologies as Ajax, jQuery, CSS, XHTML, PHP, Flash, and Flex. Wrox Professional guides are planned and written by working programmers to meet the real-world needs of programmers, developers, and IT professionals. Focused and relevant, they address the issues technology professionals face every day. They provide examples, practical solutions, and expert education in new technologies, all designed to help programmers do a better job.
Website Development / Flash, Flex, Actionscript
$59.99 USA $71.99 CAN
wrox.com Programmer Forums Join our Programmer to Programmer forums to ask and answer programming questions about this book, join discussions on the hottest topics in the industry, and connect with fellow programmers from around the world.
Code Downloads Take advantage of free code samples from this book, as well as code samples from hundreds of other books, all ready to use.
Read More Find articles, ebooks, sample chapters and tables of contents for hundreds of books, and more reference resources on programming topics that matter to you.