topical media & game development
lib-flex-store-productsView-ProductCatalogPanel.mx
lib-flex-store-productsView-ProductCatalogPanel.mx
(swf
)
[ flash
]
flex
<?xml version="1.0" encoding="utf-8"?>
<!--
//////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
// All Rights Reserved.
// The following is Sample Code and is subject to all restrictions on such code
// as contained in the End User License Agreement accompanying this product.
// If you have received this file from a source other than Adobe,
// then your use, modification, or distribution of it requires
// the prior written permission of Adobe.
//
//////////////////////////////////////////////////////////////////////////
-->
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:productsView="*"
layout="vertical"
currentState="browse"
verticalScrollPolicy="off"
horizontalScrollPolicy="off"
styleName="catalogPanel">
<mx:Metadata>
[Event(name="purchase", type="lib_flex_store_samples_flexstore_ProductThumbEvent")]
[Event(name="compare", type="lib_flex_store_samples_flexstore_ProductThumbEvent")]
[Event(name="details", type="lib_flex_store_samples_flexstore_ProductThumbEvent")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import flash.utils.Dictionary;
import mx.collections.ArrayCollection;
import mx.collections.IViewCursor;
import mx.core.DragSource;
import mx.core.IUIComponent;
import mx.effects.Effect;
import mx.effects.Fade;
import mx.effects.Move;
import mx.events.EffectEvent;
import mx.events.DragEvent;
import mx.managers.DragManager;
//import samples.flexstore.Product;
//import samples.flexstore.ProductFilter;
//import samples.flexstore.ProductFilterEvent;
//import samples.flexstore.ProductThumbEvent;
private var accepted:Dictionary = new Dictionary();
private var thumbnails:Array;
private var filterCount:int;
private var thumbnailState:String = 'browse'; //either 'browse' or 'compare'
[Bindable]
private var titleButtons:lib_flex_store_productsView_CatalogTitleButtons; //the buttons that also allow the panel to switch sides
override protected function createChildren():void
{
super.createChildren();
titleButtons = new lib_flex_store_productsView_CatalogTitleButtons();
titleBar.addChild(titleButtons);
}
override protected function layoutChrome(unscaledWidth:Number, unscaledHeight:Number):void
{
super.layoutChrome(unscaledWidth, unscaledHeight);
//when adding to a UIComponent (not a Container) need explicit width/height
titleButtons.width = unscaledWidth / 2; //make it big so as we add cart items we can stretch)
titleButtons.height = titleButtons.measuredHeight;
//this placement algorithm is pretty hacky, there are better ways that probably
//involve copying more of the Panel layoutChrome method and supporting methods
titleButtons.move(statusTextField.x - titleButtons.width, titleTextField.y);
}
[Bindable]
public var cartCount:int;
//-----------------------------
// catalog
//-----------------------------
private var _catalog:ArrayCollection;
[Bindable]
public function set catalog(c:ArrayCollection):void
{
_catalog = c;
createThumbnails();
}
public function get catalog():ArrayCollection
{
return _catalog;
}
//----------------------------------------------------------------------
// methods
//----------------------------------------------------------------------
private function createThumbnails():void
{
var i:int; //variables are hoisted up in scope so declare here to avoid warning
if (thumbnails != null)
{
for (i=0; i < thumbnails.length; i++)
{
thumbContent.removeChild(thumbnails[i]);
}
}
var row:int = 0;
var col:int = -1;
var n:int = catalog.length;
thumbnails = new Array(n);
filterCount = n;
for (i=0; i < n; i++)
{
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = new lib_flex_store_productsView_ProductCatalogThumbnail();
thumbnails[i] = thumb;
thumbnails[i].showInAutomationHierarchy = true;
thumb.product = catalog.getItemAt(i) as lib_flex_store_samples_flexstore_Product;
accepted[thumb.product] = true;
thumbContent.addChild(thumb);
thumb.addEventListener(lib_flex_store_samples_flexstore_ProductThumbEvent.PURCHASE, productThumbEventHandler);
thumb.addEventListener(lib_flex_store_samples_flexstore_ProductThumbEvent.COMPARE, productThumbEventHandler);
thumb.addEventListener(lib_flex_store_samples_flexstore_ProductThumbEvent.DETAILS, productThumbEventHandler);
thumb.addEventListener(DragEvent.DRAG_START,thumbDragStartHandler);
}
layoutCatalog();
}
private function thumbDragStartHandler(event:MouseEvent):void
{
if (DragManager.isDragging == false)
{
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = event.target as lib_flex_store_productsView_ProductCatalogThumbnail;
var ds:DragSource = new DragSource();
ds.addData(thumb.product, "product");
var di:lib_flex_store_productsView_ProductCatalogThumbnail = new lib_flex_store_productsView_ProductCatalogThumbnail();
di.product = thumb.product;
//the offset logic is honestly not the most intuitive but
//it's necessary to get the dragProxy positioned correctly
DragManager.doDrag(thumbContent, ds, event, di, -thumb.x,
-thumb.y + thumbContent.verticalScrollPosition,
0.5, false);
}
}
public function filter(productFilter:lib_flex_store_samples_flexstore_ProductFilter, live:Boolean):void
{
currentState = "browse";
thumbnailState = "browse";
var count:int=0;
var n:int = thumbnails.length;
var fadeOut:Fade = new Fade();
fadeOut.alphaFrom = 1;
fadeOut.alphaTo = .1;
var targets:Array = [];
for (var i:int = 0; i < n; i++)
{
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = thumbnails[i];
var product:lib_flex_store_samples_flexstore_Product = thumb.product;
if (productFilter.accept(product))
{
accepted[product] = true;
thumb.alpha = 1;
count++;
}
else
{
accepted[product] = false;
if (live)
{
thumb.alpha = .1;
}
else if (thumb.alpha == 1) //only fade if we hadn't started
{
targets.push(thumb);
}
}
}
productFilter.count = count;
filterCount = count;
if (targets.length > 0)
{
fadeOut.targets = targets;
fadeOut.play();
fadeOut.addEventListener(EffectEvent.EFFECT_END,
function(event:EffectEvent):void
{
layoutCatalog();
});
}
else if (!live)
{
layoutCatalog();
}
}
private function layoutCatalog():Effect
{
var tileWidth:Number;
var tileHeight:Number;
var numCols:int;
if (filterCount > 9 || currentState == "compare")
{
numCols = 4;
tileWidth = lib_flex_store_productsView_ProductCatalogThumbnail.COL_WIDTH_4;
tileWidth = currentState == "compare"
? lib_flex_store_productsView_ProductCatalogThumbnail.COMPARE_WIDTH
: lib_flex_store_productsView_ProductCatalogThumbnail.COL_WIDTH_4
tileHeight = currentState == "compare"
? height - 4
: lib_flex_store_productsView_ProductCatalogThumbnail.COL_HEIGHT_4;
}
else if (filterCount > 4)
{
numCols = 3;
tileWidth = lib_flex_store_productsView_ProductCatalogThumbnail.COL_WIDTH_3;
tileHeight = lib_flex_store_productsView_ProductCatalogThumbnail.COL_HEIGHT_3;
}
else if (filterCount <= 9)
{
numCols = 2;
tileWidth = lib_flex_store_productsView_ProductCatalogThumbnail.COL_WIDTH_2;
tileHeight = lib_flex_store_productsView_ProductCatalogThumbnail.COL_HEIGHT_2;
}
else
{
}
var row:int = 0;
var col:int = -1;
var move:Move = null;
var n:int = catalog.length;
for (var i:int = 0 ; i < n ; i++)
{
var product:lib_flex_store_samples_flexstore_Product = catalog.getItemAt(i) as lib_flex_store_samples_flexstore_Product;
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = thumbnails[i];
if (accepted[product])
{
thumb.currentState = "" + numCols + "cols";
col++;
if (col > numCols - 1)
{
row++;
col = 0;
}
var xTo:Number = col * (tileWidth + lib_flex_store_productsView_ProductCatalogThumbnail.HORIZONTAL_GAP);
var yTo:Number = row * (tileHeight + lib_flex_store_productsView_ProductCatalogThumbnail.VERTICAL_GAP);
// If the thumbnail is already visible
// animate it to its new position.
if (thumb.visible)
{
// Animate only if the position is different
// from its current position.
if (thumb.x != xTo || thumb.y != yTo)
{
move = new Move(thumb);
move.xTo = xTo;
move.yTo = yTo;
move.play();
}
}
// If the thumbnail was not previously visible, sets its
// x and y coordinates. We'll make it reappear after all
// the visible thumbnails have reached their new position.
else
{
thumb.x = xTo;
thumb.y = yTo;
thumb.includeInLayout = true;
}
}
else
{
thumb.visible = false;
thumb.includeInLayout = false;
}
}
if (!move)
{
// No visible thumbnails were animated to a new position;
// fade in newly selected thumbnails right away.
fadeInThumbnails();
}
else
{
//since movement is happening get the scrollbar back to the top
thumbContent.verticalScrollPosition = 0;
// Fade in newly selected thumbnails after the last
// visible thumbnail has moved to its new position.
move.addEventListener(EffectEvent.EFFECT_END,
function(event:EffectEvent):void
{
fadeInThumbnails();
});
}
//return the last move to watch
return move;
}
//return the last effect so we could add effectEnd handler if desired
private function fadeInThumbnails():void
{
var n:int = thumbnails.length;
var effect:Fade = new Fade();
effect.alphaTo = 1;
var targets:Array = [];
for (var i:int = 0; i < n ; i++)
{
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = thumbnails[i];
if (accepted[thumb.product] && !thumb.visible)
{
thumb.alpha = 0;
thumb.visible = true;
targets.push(thumb);
}
}
if (targets.length > 0)
{
effect.targets = targets;
effect.play();
}
}
private function showDetails(product:lib_flex_store_samples_flexstore_Product):void
{
if (currentState == "details")
{
details.product = product;
return;
}
var row:int = -1;
//should be computed using border metrics instead of hard-coding the 20, but...
var xTo:Number = thumbContent.width - lib_flex_store_productsView_ProductCatalogThumbnail.COL_WIDTH_4 - 20;
var yTo:Number;
var move:Move;
var first:Boolean = true;
var selectedThumb:lib_flex_store_productsView_ProductCatalogThumbnail;
var n:int = thumbnails.length;
for (var i:int = 0; i < n; i++)
{
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = thumbnails[i];
if (thumb.visible)
{
row++;
yTo = row * (lib_flex_store_productsView_ProductCatalogThumbnail.COL_HEIGHT_4 + lib_flex_store_productsView_ProductCatalogThumbnail.VERTICAL_GAP);
thumb.currentState = "4cols";
if (thumb.x != xTo || thumb.y != yTo)
{
move = new Move(thumb);
if (first)
{
move.addEventListener(EffectEvent.EFFECT_END,
function(event:EffectEvent):void
{
details.product = product;
currentState = "details";
});
first = false;
}
move.xTo = xTo;
move.yTo = yTo;
move.play();
}
if (thumb.product == product)
{
selectedThumb = thumb;
}
}
}
if (selectedThumb != null)
{
//make sure that the selected thumb is visible in the list on the right
move.addEventListener(EffectEvent.EFFECT_END,
function(e:EffectEvent):void
{
var curpos:int = thumbContent.verticalScrollPosition;
if (selectedThumb.y < curpos)
{
thumbContent.verticalScrollPosition = y;
}
else if (selectedThumb.y + lib_flex_store_productsView_ProductCatalogThumbnail.COL_HEIGHT_4 > curpos + thumbContent.height)
{
//this logic doesn't seem to be perfect but it will do
var diff:int = selectedThumb.y - (curpos + thumbContent.height)
thumbContent.verticalScrollPosition += diff + lib_flex_store_productsView_ProductCatalogThumbnail.COL_HEIGHT_4 + lib_flex_store_productsView_ProductCatalogThumbnail.VERTICAL_GAP;
}
});
}
}
private function productThumbEventHandler(event:lib_flex_store_samples_flexstore_ProductThumbEvent):void
{
if (event.type == lib_flex_store_samples_flexstore_ProductThumbEvent.DETAILS)
{
showDetails(event.product);
}
else if (event.type == lib_flex_store_samples_flexstore_ProductThumbEvent.BROWSE)
{
if (thumbnailState == "browse")
{
currentState = "browse";
layoutCatalog();
}
else
{
compare();
}
}
else
{
dispatchEvent(event);
}
}
public function compare(toCompare:Array=null):void
{
currentState = "compare";
thumbnailState = "compare";
if (toCompare != null)
{
var n:int = thumbnails.length;
for (var i:int = 0; i < n; i++)
{
accepted[thumbnails[i].product] = false;
}
for (i=0; i < toCompare.length; i++)
{
accepted[toCompare[i]] = true;
}
}
var lastEffect:Effect = layoutCatalog();
if (lastEffect != null)
{
lastEffect.addEventListener(EffectEvent.EFFECT_END,
function (event:EffectEvent):void
{
setCompareState();
});
}
else
{
setCompareState();
}
}
private function setCompareState():void
{
//avoid an issue if the user clicks quickly where we move into
//compare state even though we're no longer in compare
if (currentState == "compare")
{
var n:int = thumbnails.length;
for (var i:int = 0; i < n; i++)
{
var thumb:lib_flex_store_productsView_ProductCatalogThumbnail = thumbnails[i];
if (accepted[thumb.product])
{
thumb.currentState = "compare";
}
}
}
}
]]>
</mx:Script>
<mx:Binding source="cartCount" destination="titleButtons.cartCount" />
<!-- two-way binding between the states of panel title buttons and the product view state -->
<mx:Binding source="lib_flex_store_ProductsView(parentDocument).currentState" destination="titleButtons.currentState" />
<mx:Binding destination="lib_flex_store_ProductsView(parentDocument).currentState" source="titleButtons.currentState" />
<mx:Canvas width="100%" height="100%"
verticalScrollPolicy="off"
horizontalScrollPolicy="off"
paddingRight="0">
<mx:Canvas id="thumbContent" width="100%" height="100%"
horizontalScrollPolicy="off"/>
<productsView:lib_flex_store_productsView_ProductDetails id="details"
width="{lib_flex_store_productsView_ProductCatalogThumbnail.COL_WIDTH_4 * 3}"
height="100%"
visible="false"
compare="productThumbEventHandler(event)"
purchase="productThumbEventHandler(event)"
browse="productThumbEventHandler(event)" />
</mx:Canvas>
<mx:states>
<mx:State name="browse">
<mx:SetProperty name="title" value="Browse"/>
</mx:State>
<mx:State name="compare">
<mx:SetProperty name="title" value="Product Comparison"/>
</mx:State>
<mx:State name="details">
<mx:SetProperty name="title" value="Product Details"/>
<mx:SetProperty target="{details}" name="visible" value="true"/>
</mx:State>
</mx:states>
</mx:Panel>
(C) Æliens
18/6/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.