Immutability and Binding
An application I am developing makes heavy use of date ranges. I created a a simple DateRange class containing start and end dates. Besides being stored in models, date ranges are used in events and passed around to controller and service methods. I didn’t like the idea of using a relatively heavyweight subclass of EventDispatcher for these purposes, so I chose not to make DateRange bindable. But I went a step further and made it an immutable class, with no setters. This won’t be everyone’s cup of tea, but is common in the Java world, where immutable objects can help out with thread-safety, obviously not an issue in Flex.
At first, I created a companion wrapper class BindableDateRange for use in models, but it became rather messy. In the course of re-factoring it away, I ran into the well-known warning:
warning: unable to bind to property ’start’ on class ‘DateRange’ (class is not an IEventDispatcher)
In one MXML component, I was binding to the start property:
<mx:DateField selectedDate=”{range.start}” change=”useDate(event)” />
But, but, but “range” is what changes! Why can’t I bind to “range.start”? Well, you can. Once I got rid of a few unrelated bugs, I was able to verify that the binding was working (i.e. the DateField updated when the range changed. To get rid of the warning, I had to create [Bindable] getters for “start” and “end” within the component itself, and replace the expression with {this.start}. Unfortunate, but there you have it.
Update: It appears that another way to get rid of the warning is to add [Bindable(event="startChanged"] to the getter for the “start” property, without necessarily deriving from EventDispatcher (which is unnecessary since events aren’t dispatched for this read-only property).
Shapes and Sprites
In recent weeks I’ve been diving into the world of Flex custom components, and the drawing APIs. Many years ago I worked on the Transform Panel in InDesign, but it’s been a while since I did any graphics-intensive programming. It’s great fun building slick user experiences with the standard Flex components, but I’d also love to work on a juicy RIA like Buzzword or SproutBuilder.
One of my personal projects is a board game. I have the board (a subclass of UIComponent) rendering a collection of tiles, which are subclasses of Sprite. It was necessary (I think) to use Sprite, because each tile has a child text label and the underlying Graphics object doesn’t have any sort of drawText() method. The next step was to look at handling selection and dragging of tiles.
Chet Haase recently joined the Flex SDK team and posted a drawing sample called TopDrawer on his blog. This was most helpful! TopDrawer has an ArtCanvas class (also a UIComponent), and the drawn shapes (rectangles, lines etc.) are represented by subclasses of Shape added as children of the canvas.
I cheerfully copied a couple of lines from the ArtCanvas event handler for MouseDown events:
var selectPoint:Point = localToGlobal(new Point(event.localX, event.localY));
for each (var shape:ArtShape in shapes)
{
if (shape.hitTestPoint(selectPoint.x, selectPoint.y, true))
{
selectShape(shape);
break;
}
}
In my game, the hitTestPoint() call did not succeed when I clicked on a tile (a Sprite) but did, if I changed it to a Shape. The simple fix was to call event.target.localToGlobal(). So one difference between Sprites and Shapes is that a child Sprite may be the “target” property of a MouseEvent, but a Shape won’t. The code worked in TopDrawer because the the shapes are Shapes, and the localX and localY properties were already in the coordinate system of the ArtCanvas.
Before I figured this out, I tried to create an AS3-only test case with a Shape child and Sprite child. The mouse event handler wasn’t even reached. I learned (via O’Reiily’s AS3 Cookbook) that the main application class does not receive mouse events, though it does receive enterFrame, for example. So here’s a one-file non-object-oriented test that does work as expected. You can pop this in an AS3-only FlexBuilder project and run it in the debugger. I hope this is helpful to someone.
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Point;
// Testing Hit detection
public class TestHitDetection extends Sprite
{
private var _rootSprite:Sprite;
private var _circleSprite:Sprite;
private var _circleShape:Shape;
public function TestHitDetection()
{
super();
_rootSprite = new Sprite(); // This is necessary because the main application class does not receive mouse events
_rootSprite.x = 50;
_rootSprite.y = 50;
_rootSprite.graphics.beginFill(0xDDDDDD);
_rootSprite.graphics.drawRect(0,0,500,500);
_rootSprite.graphics.endFill();
_circleSprite = new Sprite();
_circleSprite.graphics.beginFill(0x00FF00);
_circleSprite.graphics.drawCircle(60,60,50);
_circleSprite.graphics.endFill();
_circleSprite.x = 10;
_circleSprite.y = 10;
_rootSprite.addChild(_circleSprite);
_circleShape = new Shape();
_circleShape.graphics.beginFill(0xFF0000);
_circleShape.graphics.drawCircle(0,0,75);
_circleShape.graphics.endFill();
_circleShape.x = 200;
_circleShape.y = 200;
_rootSprite.addChild(_circleShape);
this.addChild(_rootSprite);
_rootSprite.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
}
private function handleMouseDown(event:MouseEvent):void{
trace("mousedown");
var selectPoint:Point = event.target.localToGlobal(new Point(event.localX, event.localY));
if (_circleSprite.hitTestPoint(selectPoint.x, selectPoint.y, true)) {
trace("hit sprite");
}
if (_circleShape.hitTestPoint(selectPoint.x, selectPoint.y, true)) {
trace("hit shape");
}
}
}
}
Simple MVC Sample
I decided not to be a perfectionist, and posted the Flex sample code I wrote for my presentation to the Seattle Flex User Group last month. I hope someone finds it helpful.
It’s the beginnings of a very simple slide show editor/viewer, using the RichTextEditor and TextArea controls. I actually ran into an issue with RichTextEditor when the htmlText property is bound to external data. Vote here.
But the usefulness of the application (which has no server component currently) is not the point. Much like Joe Berkovitz and James Echmalian do here, my goal was to provide readable source code showing the MVC pattern in Flex, without using Cairngorm or any other frameworks .
You can run the application here, and right-click within it to view the source.
DDD-Digression
Eric Evans wrote an excellent book on Domain-Driven Design, a book which I evangelize at every opportunity. There’s also a fairly active discussion group. At my last job I was able to apply DDD concepts in a Java/Spring/Hibernate project.
Rather than being an exhaustive catalogue of design patterns, the book describes a few which, used together, form an architectural blueprint I find very appealing. There are the usual Value Objects and Entities, but DDD distinguishes certain entities as aggregate roots, entities which need to be globally accessible (eg. by query) rather than navigated to via other entities. You can extend a UML class diagram by drawing a line around aggregate boundaries and identifying any roots.
Then for persistence, there is the notion of a repository, a collection-like API which is considered part of the domain layer, and applies to aggregate roots rather than to all entities. A repository would have methods for adding, removing or finding entities which are determined by the design to be aggregate roots. Developers sometimes question the repository notion, finding it indistinguishable from a Data Access Object, or DAO. Although repository methods are often simple pass-throughs to a DAO, I believe that one distinction is validation and other business logic that wouldn’t be appropriate in a DAO, which can be thought of as part of the infrastructure rather than the domain. For example, UserRepository.addUser(User user) might throw a DuplicateUserException. I’d use generic DAOs or drop the DAO implementation detail before I dropped the repository abstraction. With the automatic persistence of “dirty” object graphs in an ORM like Hibernate, this pattern works very cleanly. When creating new aggregates, persist the root by adding it to the repository, and let the ORM take care of the rest. Note the absence of methods called “save”.
I’m not sure yet whether DDD has much applicability to the client side of RIAs, but I find it to be a helpful tool in designing object models, including persistence.
Computed properties and binding
Updating views in response to model changes is a snap with Flex data binding.
A model property is set to be [Bindable] and and a view property is set to an expression in curly braces that refers to that property. For example, suppose MyModel.as has the following:
[Bindable] public var description: String;
Then MyView.mxml can have this:
<mx:Panel title="{model.description}">
...
</mx:Panel>
Flex implements this by dispatching a PropertyChangedEvent when “description” changes, but by implementing implicit getter and setter functions, one can customize the event. In the MVC sample I wrote, which I hope to post one of these days, I stumbled upon the following idiom, where several bindable properties are based on a single value changing. In SlideEditorModel.as:
public function selectSlides(slides: Array) : void {
if (slides == null || slides.length == 0) {
_selectedSlides = null;
} else {
_selectedSlides = slides;
}
dispatchEvent(new Event("selectionChanged"));
}
[Bindable(event="selectionChanged", type="flash.events.Event")]
public function get currentSlide() : Slide {
if (_selectedSlides == null) {
return null;
} else {
return _selectedSlides[0];
}
}
[Bindable(event="selectionChanged", type="flash.events.Event")]
public function get hasSingleSelection() : Boolean {
return _selectedSlides != null && _selectedSlides.length == 1;
}
[Bindable(event="selectionChanged", type="flash.events.Event")]
public function get hasSelection() : Boolean {
return _selectedSlides != null && _selectedSlides.length > 0;
}
With the above in place, I was able to enable buttons based on “hasSelection” or “hasSingleSelection” in addition to binding to the current Slide.
<mx:Button label="Duplicate Slide"
click="handleDuplicateSlide(event)"
enabled="{slideShowEditorModel.hasSingleSelection}"/>
SeaFlex
On Thursday, January 10th, I’ll be presenting at the Seattle Flex User Group. I’m working on a little sample application, and will use it to illustrate the MVC design pattern. Details of the meeting are here.
Flex and OpenDoc
This post is about in-place editing with user-selected editors.
In the early 90s I had the privilege of working with some wonderful developers on a project at Apple called OpenDoc. The Wikipedia description is decent. We made many mistakes and the project was ultimately canceled when Steve Jobs returned to Apple, but I still long for the user experience and component market we were trying to create, and I wonder if it will happen on the Web, and what that will look like.
In a nutshell, OpenDoc allowed you to edit a compound document (eg. image embedded in text) using the editors of your choice (based on preferences), and without leaving the document context or doing any import/export operations. The whole system was document-centric; you never launched an application – just opened documents (or stationery, if creating a new document).
Microsoft’s OLE was supposed to accomplish the same thing, but the user experience was poor. In OpenDoc documents, a single click on an image would activate the image (no matter how deeply nested), and the tools of your preferred image editor would appear.
A structured storage API allowed each editor to insert and extract its portion of the document data. Importantly, the overall document type did not need to be linear text or page layout – it could be a spreadsheet. Nor did all editors need to support the embedding of others – but all were embeddable in those that did.
I like to think this is still something users would appreciate, but our applications are still monoliths, for the most part. Adobe has worked hard to integrate the applications in its Creative Suite, but they’re still separate (and large) applications.
Today, Flex is enabling the development of sophisticated online authoring tools. Examples include Buzzword for word-processing, and Picnik for image editing. The ambitious Aviary project appears to be re-creating the entire Adobe Creative Suite!
The Picnik image editor does a nice job of integrating with different photo-sharing services. But wouldn’t it be nice if I could use the Picnik tools (or those of any other image editor) to edit images embedded in my Buzzword document?
Adobe has an excellent platform with Flash, Flex and AIR. It includes a decent imaging model, and the infrastructure for shared components (Modules and RSLs). I’d love to see an open extensible authoring platform emerge, rather than a series of rich, but monolithic applications.
Welcome!
Flexygen is dedicated to building Rich Internet Applications (RIAs) using Adobe Flex and the Adobe Integrated Runtime (AIR). Stay tuned for further information.
-
Recent
-
Links
-
Archives
- May 2008 (1)
- March 2008 (1)
- February 2008 (2)
- January 2008 (2)
- December 2007 (2)
-
Categories
-
RSS
Entries RSS
Comments RSS