| <?xml version="1.0" encoding="utf-8"?>
|
| <!-- |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| --> |
| <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
|
| xmlns:productsView="productsView.*"
|
| layout="vertical"
|
| currentState="browse"
|
| verticalScrollPolicy="off"
|
| horizontalScrollPolicy="off"
|
| styleName="catalogPanel">
|
|
|
| <mx:Metadata>
|
| [Event(name="purchase", type="samples.flexstore.ProductThumbEvent")]
|
| [Event(name="compare", type="samples.flexstore.ProductThumbEvent")]
|
| [Event(name="details", type="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:CatalogTitleButtons; //the buttons that also allow the panel to switch sides
|
|
|
| override protected function createChildren():void
|
| {
|
| super.createChildren();
|
| titleButtons = new 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:ProductCatalogThumbnail = new ProductCatalogThumbnail();
|
| thumbnails[i] = thumb;
|
| thumbnails[i].showInAutomationHierarchy = true;
|
| thumb.product = catalog.getItemAt(i) as Product;
|
| accepted[thumb.product] = true;
|
| thumbContent.addChild(thumb);
|
| thumb.addEventListener(ProductThumbEvent.PURCHASE, productThumbEventHandler);
|
| thumb.addEventListener(ProductThumbEvent.COMPARE, productThumbEventHandler);
|
| thumb.addEventListener(ProductThumbEvent.DETAILS, productThumbEventHandler);
|
| thumb.addEventListener(DragEvent.DRAG_START,thumbDragStartHandler);
|
| }
|
|
|
| layoutCatalog();
|
| }
|
|
|
| private function thumbDragStartHandler(event:MouseEvent):void
|
| {
|
| if (DragManager.isDragging == false)
|
| {
|
| var thumb:ProductCatalogThumbnail = event.target as ProductCatalogThumbnail;
|
| var ds:DragSource = new DragSource();
|
| ds.addData(thumb.product, "product");
|
|
|
| var di:ProductCatalogThumbnail = new 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: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:ProductCatalogThumbnail = thumbnails[i];
|
| var product: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 = ProductCatalogThumbnail.COL_WIDTH_4;
|
| tileWidth = currentState == "compare"
|
| ? ProductCatalogThumbnail.COMPARE_WIDTH
|
| : ProductCatalogThumbnail.COL_WIDTH_4
|
| tileHeight = currentState == "compare"
|
| ? height - 4
|
| : ProductCatalogThumbnail.COL_HEIGHT_4;
|
| }
|
| else if (filterCount > 4)
|
| {
|
| numCols = 3;
|
| tileWidth = ProductCatalogThumbnail.COL_WIDTH_3;
|
| tileHeight = ProductCatalogThumbnail.COL_HEIGHT_3;
|
| }
|
| else if (filterCount <= 9)
|
| {
|
| numCols = 2;
|
| tileWidth = ProductCatalogThumbnail.COL_WIDTH_2;
|
| tileHeight = 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:Product = catalog.getItemAt(i) as Product;
|
| var thumb:ProductCatalogThumbnail = thumbnails[i];
|
| if (accepted[product])
|
| {
|
| thumb.currentState = "" + numCols + "cols";
|
| col++;
|
| if (col > numCols - 1)
|
| {
|
| row++;
|
| col = 0;
|
| }
|
|
|
| var xTo:Number = col * (tileWidth + ProductCatalogThumbnail.HORIZONTAL_GAP);
|
| var yTo:Number = row * (tileHeight + 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: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: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 - ProductCatalogThumbnail.COL_WIDTH_4 - 20;
|
| var yTo:Number;
|
|
|
| var move:Move;
|
| var first:Boolean = true;
|
| var selectedThumb:ProductCatalogThumbnail;
|
|
|
| var n:int = thumbnails.length;
|
| for (var i:int = 0; i < n; i++)
|
| {
|
| var thumb:ProductCatalogThumbnail = thumbnails[i];
|
| if (thumb.visible)
|
| {
|
| row++;
|
| yTo = row * (ProductCatalogThumbnail.COL_HEIGHT_4 + 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 + 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 + ProductCatalogThumbnail.COL_HEIGHT_4 + ProductCatalogThumbnail.VERTICAL_GAP;
|
| }
|
| });
|
| }
|
|
|
| }
|
|
|
| private function productThumbEventHandler(event:ProductThumbEvent):void
|
| {
|
| if (event.type == ProductThumbEvent.DETAILS)
|
| {
|
| showDetails(event.product);
|
| }
|
| else if (event.type == 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: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="ProductsView(parentDocument).currentState" destination="titleButtons.currentState" />
|
| <mx:Binding destination="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:ProductDetails id="details"
|
| width="{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>
|