Apr 26, 2007 . Comments (0)
In any business or economics course I took while attending university a fictitious widget was almost always used as the 'product' when the professor need to explain a concept or idea. I find it ironic now that 'widgets' seem to be one of the hottest products on the internet. Everywhere you look you see them, for music, for videos, for pictures, for what you're up to, you name it. Windows Vista and Mac OSX support them. Google and Yahoo offer them. Mainstream media is covering them, Om Malik has written a few articles about them for Business 2.0, here and here. So what does this mean for Flash and Flex developers? A lot.
The Flash player is the perfect platform for widgets, light weight, highly visual, easy to use, across the board browser support. The only knock against using the Flash player for desktop widgets was that you could not easily access the local machine for reading and writing data. With the release of Apollo that argument went down the tubes. The market for widgets is hot and Flash developers are likely the most coveted group of developers to meet the demand for widget development, whether it's browser based or desktop based.
Currently most of the demand for widgets seems to be consumer driven. People want widgets for their desktop to track the news, weather, stocks, etc. or to dress up their web pages and blogs. Based on how quickly they are getting adopted and how useful they can be I think it is only a matter of time before the business world widely adopts them for business use. All businesses have metrics, key performance indicators (kpi's), they need to track and monitor, widgets are a perfect solution. That will open up a whole new world of opportunities for Flash developers. That's the wedge into the business world's door. Once businesses see how quickly these 'widgets' can be rolled out and how useful they can be, I do not think it would be a huge leap to expect wider adoption of Flash based applications in general within the business community.
Companies of all sizes have a huge appetite for custom applications. Widgets are the perfect snack to whet their appetites. Get them comfortable with morsels of Flash based widgets, and then roll out the main course of Apollo and Flex based enterprise level applications. The future for Flash and Flex developers certainly looks bright.
Apr 19, 2007 . Comments (10)
As promised here is the source code for the 3d flex form component I created using the Papervision engine. You can download the entire project sample code here. I should have some feature updates to this in the near future. Enjoy.
/**************************************************************
CubeForm.as
Created by: Derrick Grigg
derrick@dgrigg.com
http://www.dgrigg.com
Created on: April 18, 2007
Version: 1.0.0
This is released under a Creative Commons license. More information can be found here:
http://creativecommons.org/licenses/by/2.5/
***************************************************************/
package com.dgrigg.containers
{
import flash.display.Sprite;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.utils.clearInterval;
import flash.utils.setInterval;
import flash.geom.Point;
import flash.events.Event;
import mx.core.UIComponent;
import mx.containers.ViewStack;
import mx.core.Container;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.objects.Plane;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.materials.BitmapMaterial;
/**
* Dispatched after cube rotates to selected side.
*/
[Event(name="change",type="flash.events.Event")]
/**
* Renders a view stack as a 3d cube using the PaperVision engine.
* The cube can rotate to the 6 different sides to display a view
* from the view stack and allow the user to interact with it.
*/
public class CubeForm extends UIComponent
{
/**
* Papervision camerea.
*/
protected var camera:Camera3D;
/**
* Papervision scene.
*/
protected var scene:Scene3D;
/**
* Sprite that form is rendered in.
*/
protected var sprite:Sprite;
/**
* Value to store a reference to the rotation interval.
*/
protected var intRotate:int;
/**
* @private
*/
protected var _selectedIndex: int = 0;
/**
* Array of the six planes that make up the cube.
* Each item in the array consists of a <code>plane</code> which is the Papervision Plane
* and a <code>view</code> which is a reference to the child in the view stack.
*/
protected var planes:Array;
/**
* The view stack the form is bound to.
*/
protected var _viewStack:ViewStack;
/**
* Root display object for the cube form.
*/
protected var rootNode:DisplayObject3D;
/**
* Number of steps used to rotate the cube;
*
* @default 10
*/
public var rotationSteps:int = 10;
/**
* @param canvas The sprite to generate the cube form in.
*/
public function CubeForm(canvas:Sprite)
{
sprite = canvas;
init();
}
private function init():void
{
var s:Stage = sprite.stage;
s.quality = StageQuality.BEST;
setupScene();
}
private function setupScene():void
{
scene = new Scene3D(sprite);
camera = new Camera3D();
camera.zoom = 10;
camera.focus = 100;
rootNode = scene.addChild(new DisplayObject3D(), 'rootNode');
scene.renderCamera(camera);
}
/**
* The view stack that is bound to the cube form.
* <ul>
* <li>The view stack must have atleast six children, otherwise an error will be thrown.</li>
* <li>The view stack must have it's <code>creationPolicy</code> property set to <code>all</code>.</li>
* <li>Each view in the view stack must have it's width and height explicity set,
* otherwise the view does not get rendered properly during initialization.</li>
* </ul>
*
*/
public function set viewStack (vs:ViewStack):void
{
var plane: Plane;
var view:Container;
planes = new Array();
_viewStack = vs;
//check to see if there are 6 views in the view stack
//if no throw an error
var children:Array = _viewStack.getChildren();
if (children.length > 5)
{
view = children[0] as Container;
plane = createPlane(view, 0 , 0 ,-(_viewStack.width/2));
rootNode.addChild(plane, 'plane0');
planes.push({plane:plane, view:view});
view = children[1] as Container;
plane = createPlane(view, (_viewStack.width/2), 0, 0, 0, 270, 0);
rootNode.addChild(plane, 'plane1');
planes.push({plane:plane, view:view});
view = children[2] as Container;
plane =createPlane(view, 0, 0, (_viewStack.width/2), 0, 180, 0);
rootNode.addChild(plane, 'plane2');
planes.push({plane:plane, view:view});
view = children[3] as Container;
plane =createPlane(view, -(_viewStack.width/2), 0, 0, 0, 90, 0);
rootNode.addChild(plane, 'plane3');
planes.push({plane:plane, view:view});
view = children[4] as Container;
plane =createPlane(view, 0, (_viewStack.width/2), 0, 270, 0, 0);
rootNode.addChild(plane, 'plane4');
planes.push({plane:plane, view:view});
view = children[5] as Container;
plane =createPlane(view, 0, -(_viewStack.width/2), 0, 90, 0, 0);
rootNode.addChild(plane, 'plane5');
planes.push({plane:plane, view:view});
//render the scene to get determine the rendered dimensions
scene.renderCamera( camera );
//position the view stack directly over the cube form
var p:Point = sprite.localToGlobal(new Point(0,0));
_viewStack.x = p.x - _viewStack.width/2;
_viewStack.y = p.y - _viewStack.height/2;
//adjust the camera zoom to compensate for any scaling that has occurred,
//in order to keep the cube at the exact scale of the view stack
camera.zoom = (_viewStack.width/sprite.width)*10;
scene.renderCamera(camera);
} else {
var error:Error = new Error('Error: the viewStack must contain at least six children');
throw(error);
}
}
/**
* @private
*/
protected function createPlane(panel:UIComponent, pX:int=0, pY:int=0, pZ:int=0, pRotationX:int=0, pRotationY:int=0, pRotationZ:int=0):Plane
{
var bmp:BitmapData = new BitmapData(_viewStack.width, _viewStack.height);
bmp.draw(panel);
var material:BitmapMaterial = new BitmapMaterial(bmp);
material.smooth = true;
var initObj:Object = {x:pX, y:pY, z:pZ, rotationX:pRotationX, rotationY: pRotationY, rotationZ:pRotationZ};
var plane:Plane = new Plane(material, bmp.width, bmp.height, 4, 4, initObj);
return plane;
}
/**
* Index of selected plane on the cube form.
*
* @default 0
*/
public function get selectedIndex():int
{
return _selectedIndex;
}
public function set selectedIndex(val:int):void
{
//get the selected plane and re-render that side in order
//to reflect the current data on the view
var obj:Object = planes[_selectedIndex];
var plane:Plane = obj.plane as Plane;
var bmp:BitmapData = new BitmapData(obj.view.width, obj.view.height);
bmp.draw(obj.view);
var material:BitmapMaterial = new BitmapMaterial(bmp);
plane.material = material;
this.scene.renderCamera( camera );
obj.view.visible = false;
//get the current cube's rotation values
_selectedIndex = val;
var curRotation:Object = {x:rootNode.rotationX, y:rootNode.rotationY, z:rootNode.rotationZ};
var destRotation:Object;
switch (_selectedIndex)
{
case 0:
destRotation = {x: 0, y:0, z:0};
break;
case 1:
destRotation = {x: 0, y:90, z:0};
break;
case 2:
destRotation = {x: 0, y:180, z:0};
break;
case 3:
destRotation = {x: 0, y:270, z:0};
break;
case 4:
destRotation = {x: 90, y:0, z:0};
break;
case 5:
destRotation = {x: 270, y:0, z:0};
break;
}
var steps: Object = new Object();
steps.x = Math.floor((destRotation.x - curRotation.x)/rotationSteps);
steps.y = Math.floor((destRotation.y - curRotation.y)/rotationSteps);
steps.z = Math.floor((destRotation.z - curRotation.z)/rotationSteps);
intRotate = setInterval(rotateCube, 50, steps, destRotation);
}
private function rotateCube(steps:Object, dest:Object):void
{
if (Math.abs(rootNode.rotationX) != dest.x) rootNode.rotationX += steps.x;
if (Math.abs(rootNode.rotationY) != dest.y) rootNode.rotationY += steps.y;
if (Math.abs(rootNode.rotationX) != dest.z) rootNode.rotationZ += steps.z;
scene.renderCamera( camera );
if (Math.abs(rootNode.rotationX) == dest.x && Math.abs(rootNode.rotationY) == dest.y && Math.abs(rootNode.rotationZ) == dest.z)
{
//reset the rotation values back to a value <= 360
if (Math.abs(rootNode.rotationX) >= 360) rootNode.rotationX = Math.abs(rootNode.rotationX) - 360;
if (Math.abs(rootNode.rotationY) >= 360) rootNode.rotationY = Math.abs(rootNode.rotationY) - 360;
if (Math.abs(rootNode.rotationZ) >= 360) rootNode.rotationZ = Math.abs(rootNode.rotationZ) - 360;
clearInterval(intRotate);
_viewStack.selectedIndex = _selectedIndex;
dispatchEvent(new Event(Event.CHANGE));
}
}
}
}
Apr 18, 2007 . Comments (19)
I just started experimenting with Papervision and wanted to try something I have not seen yet, integrating a Flex view into the 3d realm. There are plenty of samples of really neat looking cars, animals and shapes getting rotated every which way but nothing that shows how to blend something applicationish (is that word?) like a data entry form with 3d to give the user a new experience.
I created a simple component that gets bound to a ViewStack, generates the six sides of a cube and then renders the new cube form. Even though Papervision depends on Bitmaps and static snapshots of things to perform the rendering I was able work editable Flex UI's into the 3d by performing a bit of magic with the ViewStack and positioning.
I have to say, using Papervision to do this stuff is so nice. It only took a few hours to work out the kinks to get this working, and that included figuring out how to use Papervision in the first place. The team who created it deserves a ton of kudos. I'll release the source code soon, I just need to do some cleanup and commenting.
Papervision really opens up a whole new world of potential in Flash/Flex, not just for displaying things, but for how users can even interact with interfaces (even boring data entry forms). It will be exciting to see what people come up with, imagination is the only limit.
Apr 12, 2007 . Comments (7)
One of the things that really leaves me scratching my head when it comes to Cairngorm is updating a view based on something happening in a command that changes the value or state of the model. I know with release of Cairngorm that coincided with the release of Flex 2 the ViewHelper and ViewLocator were essentially deprecated in favour of using Flex's excellent data binding in conjunction with the ModelLocator. I have no issues with that concept, it works well most of the time .... well maybe some off the time. Things can start to get really ugly, really fast when you start introducing use cases that are beyond anything but the simplest, and developers like me start contemplating harakiri.
Background
Before I go to far into proposing a solution it would be good to quickly look at what the currently proposed options are. Alex Uhlmann has put together some excellent
articles on how to update Views from Commands, below are three basic concepts and a proposal pulled from his articles.
- The concept of saving a id in a command to reference the VO being updated.
We just need to send the unique identifier to the Command, since the Command itself needs to retrieve a StockQuote object from the manager object. So the GetStockQuoteEvent
class gets another quoteId property. In GetStockQuoteCommand we need to store the unique identifier as an instance property since the command needs to retrieve and update the
correct StockQuote object in an onResult or onFault handler.
- The concept of a value in the VO to track the state of the value object.
Usually, while a remote service is being processed, the UI is supposed to react to it with i.e. displaying a progress or disabling certain UI components to let the user know
that further requests cannot be dispatched. In our use case we'd like to disable the "Get Quote" button while a remote service call is being processed. We can achieve this
easily with adding a property to our representing model object from iteration 2 StockQuote. Let's call it isPending and make it a Boolean where our view can bind to.
- The concept of using data binding to update the view based on changes to the model and/or value objects.
Basically, your views bind to properties that are retrieved from your ModelLocator. These properties can be changed from your Commands, other business logic or other views and
once changed; all listening views are updated seamlessly.
- Proposal to use events from the model to update the view, but notes many limitations.
I see two drawbacks using this approach. First of all, it’s more code to write and most importantly, to read. This is
because your views need to subscribe manually to events and define event handlers. It might also affect your models since some of them might need extra EventDispatcher
functionality. Secondly, we need to make sure that our model objects exist when the view is subscribing to the event.
The first three concepts essentially boil down to using data binding and the model locator and/or value objects to communicate to the view that something visually needs to
change to reflect a change in the data model. This is the method that seems to be favoured by Adobe Consulting, and for many things it works perfectly, ie binding a display form to a value object. The proposal with events from the model has no traction, and in
that article, Alex points to the Observable pattern but it is essentially the same as using data binding with a ChangeWatcher (if you ask me), which is what concepts 1-3 can be boiled down to.
Scenario
Now the we have a quick understanding of the options available, let's take a quick look at the implementation and where it starts to get ugly. For this example let's say we
have a value object, I'll call it a 'Widget' for agrument's sake, that we need to have saved on a server somewhere. The user clicks a save button, the view changes state to
reflect the fact the Widget is being saved (the form gets disabled and a progress indicator is displayed to show something is happening) and the process of getting the server to perform the update starts (all the normal Cairngorm process and flow here). The server responds back that either, a) the Widget was saved, or b) something went wrong and the Widget was not saved. The Command that was used to perform the save (since it implements the Responder interface) handles the result or fault. Everything is straight forward so far.
Use case 1
If the Widget saved successfully the progress indicator needs to goes away, and the view that was editing the Widget is enabled to allow the user to continue working.
Use case 2
If the Widget was not saved (an error occurred), the progress indicator needs to go away, an Alert needs to get displayed to tell the user something bad happened, and something on the view that was editing the Widget needs to reflect the fact that an error occurred (some sort of visual cue).
Implementation
Using data binding and the ModelLocator (the combination of the first three concepts above) the implementation would look something like this. Create a property in the model
called 'widget' that contains a Widget instance. The Widget has a property state with a value of -1, 0, or 1 depending on whether or not the Widget was saved (I use three values to give the Widget a stateless mode, ie the user has not yet tried to save the Widget, or the Widget is in the process of being saved).
package com.dgrigg.vo
{
import com.adobe.cairngorm.vo.ValueObject;
public class WidgetVO implements ValueObject
{
//values to reflect state
public static const DEFAULT:int = 0;
public static const SAVED:int = 1;
public static const ERROR:int = -1;
[Bindable]
public var id: int = 0;
[Bindable]
public var name: String;
[Bindable]
public var description: String;
[Bindable]
public var state: int = DEFAULT;
public function WidgetVO()
{
}
}
}
I can use databinding and a ChangeWatcher to watch the value and when it changes to 1 I
know the Widget was saved, a value of -1 and I know it failed. The ChangeWatcher will dispatch an event that the view is listening for. This gets ugly in two places. In the Command I need to determine what has happened and update the state of the Widget accordingly.
//code in the Command that is handling the server response
private function onResult(event:ResultEvent):void
{
//do some stuff
widget.state = WidgetVO.SAVED;
}
private function onFault(event:FaultEvent):void
{
//do some stuff
widget.state = WidgetVO.ERROR;
}
I also need some type of conditional statement in the handling method in the view to deal with the different states the Widget could have, just looks and seems ugly to me.
//code in the View that is editing the Widget
...
watcher = ChangeWatcher.watch(model.widget, 'widgetSaved', widgetStateHandler);
...
private function widgetStateHandler(event:PropertyChangeEvent):void
{
if (event.newValue == WidgetVO.SAVED)
{
//change the state to reflect the widget was saved
}
else if (event.newValue == WidgetVO.ERROR)
{
//handle an error
}
else if (event.newValue == WidgetVO.DEFAULT)
{
//do nothing
}
}
This solution seems very fragile to me. The state of the Widget is being handled in multiple places and it seems like a disaster waiting to happen. If the view is bound to a selected widget (ie the widget can change at runtime), then this solution gets worse because you need to add in handling the case when the widget is changed,
you need to unwatch the old widget and then watch the new widget. Another twist on this solution could be the Observable
(and here) pattern but it is essentially putting lipstick on a pig, it looks and smells exactly like the databinding/ChangeWatcher solution.
A better solution
Ideally there must be a way for the view to listen to an event to indicate the state change in the widget. Jon at Firemoss had an interesting idea of dispatching events when commands have affected the model and having the view(s) listen for the events, but even by his own admission it was a little hackish and not an ideal solution. Thankfully Jay posted a comment about a solution he had to solve this problem and Joe was kind enough to share it.
The basic concept is a ViewController, that is instantiated in the View. The view registers listeners to the events the ViewController dispatches. The ViewController listens to Cairngorm Events dispatched from the FrontController (which orginate in the result/fault handlers in a Command) and then dispatches out Flash Events, that the view is listening for. This seems like a nice clean solution, it nicely keeps the separation between the view and the controller/command and it gets around having large conditional statements in the View to deal with Model changes. To quote Jon
It's a bit like ViewHelper, but backwards enough where it works implicitly instead of explicitly.
Using the ViewController still leaves one open to the issue of dealing with the case where many Widgets are open in different views. All instances of the ViewController will
'hear' the events whenever the FrontController dispatches an event to note a change in the state of a widget, and all the views will react, whether or not they are the view that is bound to that particular Widget. A simple solution for this is to pass along a unique id that is tied to the View and stored in the Command (something Alex proposed above in a slightly
different fashion). The ViewController would receive the view's id as part of the event it hears and then pass that id along in the event it dispatches that the view receives.
The view could do a simple check to determine if it is the view that should be affected, and then act accordingly.
Thanks to Jay (the originator of the idea) and Joe, nice job and kudos for hopefully saving many people who use Cairngorm from committing harakiri when it comes time to update views based on model state changes.