I have one list, which the item render it`s like this:link.
But now I need to enable or disable the button delete depends the view state which my List is inside.
This is my view(which contains the list):
<s:states>
<s:State name="main" /> <!-- Navigation.CART_MAIN -->
<s:State name="cash" /> <!-- Navigation.CART_CASH -->
<s:State name="credit" /> <!-- Navigation.CART_CREDIT -->
</s:states>
<s:List id="theList"
width="480" height="240"
x="69" y="82"
dataProvider="{model.products}"
useVirtualLayout="false"
itemRenderer="com.png.vm.ui.components.ProductCartThumbnail" >
</s:List>
The thing is that I just want to enable the delete buttons inside the itemRender when the screen is using the state "main"
Another option would be to create separate itemRenderers and use the itemRendererFunction.
I've taken this example from similar question that was asked earlier and modified it a bit to suit your needs:
flex 4 list ItemRenderer: how can i have different itemrenderers for different rows?
<fx:Script>
<![CDATA[
import renderers.*;
import mx.core.ClassFactory;
import spark.skins.default.DefaultItemRenderer;
private function list_itemRendererFunc(item:Object):ClassFactory {
var cla:Class = MainItemRenderer;
switch (currentState) {
case "main":
cla = MainItemRenderer;
break;
default:
cla = CashCreditItemRenderer;
break;
}
return new ClassFactory(cla);
}
]]>
</fx:Script>
<s:List id="theList"
x="69" y="82"
itemRendererFunction="list_itemRendererFunc"
dataProvider="{model.products}"
useVirtualLayout="false">
EDIT:
Here's the other solution that was used. You can designate different itemRenderers by declaring different property values for each state.
<s:List id="theList"
width="393" height="223"
x="42" y="69"
dataProvider="{model.products}"
useVirtualLayout="false"
itemRenderer.main="com.png.vm.ui.components.ProductCartThumbnail"
itemRenderer="com.png.vm.ui.components.ProductCartThumbnailReadOnly">
I got exactly the same problem.
I injected the model state (modelState for example) which determines the state of the buttons in the renderer class.
<s:ItemRenderer>
<fx:Script>
<![CDATA[
import spark.components.List;
[Bindable]
public var modelState:String;
public function deleteItem():void {
var parentList:List = owner as List;
// remove the item
parentList.dataProvider.removeItemAt(parentList.dataProvider.getItemIndex(data))
}
]]>
</fx:Script>
<s:HGroup>
<s:Label text="{data}" />
<s:Button id="remove" label="X" click="deleteItem()"
enable="{modelState=='main'}"/>
</s:HGroup>
</s:ItemRenderer>
Yes, I know that this is not the best decision!
Related
I have an ItemRenderer in which the selected state should be disabled (I'm using the renderer states and I don't have a selected state).
The problem is that the list (spark) resets the item renderer state upon click even though I don't have a "selected" state.
I want to fully prevent this behavior but I don't know how.
My renderer has autoDrawBackground set to false but it has to be enabled (although enabled=false fixes this issue)
Also, the renderer has several children including a list of its own.
Setting mouseEnabled="false" on the renderer fixes the renderer itself but not its children, and I need some of the children to be mouse enabled.
Edit:
Following is an excerpt from my item renderer:
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
width="100%" autoDrawBackground="false">
<s:states>
<s:State name="normal" />
<s:State name="suitable" />
<s:State name="mine" />
<s:State name="deleted" />
</s:states>
<s:Rect id="rect" top="0" right="0" bottom="0" left="0">
<s:fill>
<s:SolidColor id="background"
alpha=".8" alpha.deleted=".4"
color="0xff0000" color.suitable="0x00ff00" color.mine="0x0000ff" />
</s:fill>
</s:Rect>
<s:Label id="name" left="4" top="4" right="40" />
<s:List id="myList" left="4" top="40" right="4"
contentBackgroundAlpha="0" borderVisible="false" horizontalScrollPolicy="off">
<s:layout>
<s:VerticalLayout gap="3" paddingBottom="4" requestedMinRowCount="2" />
</s:layout>
</s:List>
</s:ItemRenderer>
Second Edit:
I had the same problem with the mouse hover state but that seems to have a workaround:
override protected function set hovered(value:Boolean) : void
{
// do nothing (prevent current state from changing to "hovered" state)
}
I'm unclear if you want to prevent an item from being selected; or just prevent the visual properties that go along with an item being selected.
To handle the visual aspects; I'd try to override the getCurrentRendererState() method. Something like this:
override protected function getCurrentRendererState():String{
if (selected && hasState("selected"))){
return "normal"; // or whatever state you want it to be
}
if (selected && down && hasState("downAndSelected")){
return "normal"; // or whatever state you want it to be
}
super.getCurrentRendererState()
}
In theory, this should prevent your renderer from ever going into a selected state.
I have 2 views.
This is the 1-st view.
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="MTA">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import adobe.utils.CustomActions;
import data.MTA_Item;
[Bindable]
public static var actions:ArrayCollection;
public var firstItem:MTA_Item;
public var secondItem:MTA_Item;
public var thirdItem:MTA_Item;
public function initItems():ArrayCollection
{
actions = new ArrayCollection();
firstItem = new MTA_Item(1,"Test","Test note");
secondItem = new MTA_Item(2,"DevCom","My First Notes");
thirdItem = new MTA_Item(3,"Auto" , "BMW");
actions.addItem(firstItem);
actions.addItem(secondItem);
actions.addItem(thirdItem);
return actions;
}
override public function set data(value:Object):void
{
if(actions == null)
initItems();
}
]]>
</fx:Script>
<s:VGroup>
<s:Label text="was" >
</s:Label>
</s:VGroup>
<s:List id="items" left="0" right="0" top="0" bottom="0" dataProvider="{actions}" change="navigator.pushView(FormDetails, items.selectedItem)">
<s:itemRenderer>
<fx:Component>
<s:IconItemRenderer label="ID:{data.ID}Name:{data.Name}" />
</fx:Component>
</s:itemRenderer>
</s:List>
</s:View>
2-nd view.
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import views.FlexMobileTestAppHomeView;
public function list_changeHandler(event:Event):void
{
var name:String = _namechange.text;
var notes:String = _note.text;
navigator.pushView(FlexMobileTestAppHomeView,data);
}
override public function set data(value:Object):void
{
}
]]>
</fx:Script>
<s:navigationContent>
<s:Button label="Back"
click="navigator.popToFirstView()"/>
</s:navigationContent>
<s:actionContent >
<s:Button label="save" click="list_changeHandler(event)" />
</s:actionContent>
<s:HGroup verticalAlign="middle" gap="12">
<s:VGroup>
<s:HGroup>
<s:Label text="Name:" />
<s:TextInput id="_namechange" text="{data.Name}">
</s:TextInput>
</s:HGroup>
<s:HGroup>
<s:Label text="Notes:"/>
<s:TextArea id="_note" text="{data.Notes}" />
</s:HGroup>
</s:VGroup>
</s:HGroup>
</s:View>
I need pass data to 2-nd.Then i must edit "name" , and "notes" and button save must return new data to 1-st view. How i can get "actions" from 1-st view and edit?
There are lots of ways to share data between two components. I wrote this blog post about the Flex facilities for sharing data between two Flex Components; however what I think you will be best suited with something more specific.
I'd look into these options:
Override the createReturnObject method in the second view. More information here. Be sure to read the comments for information about accessing the data in recent Flex versions.
Use a framework that supports Dependency Injection. RobotLegs and Swiz are two options I've used in the past. Basically, create a model object that contains a reference to a selectedItem and inject that into both views. Many would claim that this is the best way.
Store a reference to the selected object in a static variable; so that you can reference it in both views. This is a lot less of a learning curve than number 1.
Since you are already passing a reference to the component in the pushView method; are you sure the reference isn't automatically updated when you go back? Really both views are in essence using the same memory reference to get at the data. You may have to refresh the list for the list to pick up on the changes, though. Or I could be wrong on this one.
I got a question about event dispatching and dispatching in flex.
My goal is to pass an email address captured in one component (Comp1) and be passed to another (Comp2). These two components are navigator content of a viewstack. The custom event dispatched the email correctly from Comp1 but somehow could not reach Comp2. The application (Main) looks like :
<fx:Script>
<![CDATA[
import events.LoginEventComplete;
protected function loginHandler(event:LoginEventComplete):void
{
myViewStack.selectedChild = idViewMain;
}
]]>
</fx:Script>
<s:Panel width="100%" height="100%">
<mx:ViewStack id="myViewStack" selectedIndex="0" width="100%" height="100%">
<views:Comp1 id="idViewLogin" login="loginHandler(event)"/>
<views:Comp2 id="idViewMain"/>
</mx:ViewStack>
</s:Panel>
The Comp1 looks like
<fx:Metadata>
[Event(name="login", type="events.LoginEventComplete")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import events.LoginEventComplete;
protected function bnLogIn_clickHandler(event:MouseEvent):void{
var e:LoginEventComplete = new LoginEventComplete("login");
e.workemail = tiWorkEmail.text;
dispatchEvent(e);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:layout>
<s:VerticalLayout verticalAlign="middle" horizontalAlign="center"/>
</s:layout>
<s:Panel width="400" height="500">
<s:Label text="Email" x="130" y="150"/>
<s:TextInput id="tiWorkEmail" top="140" left="180" right="60"/>
<s:Button
id="bnLogIn"
label="Log In"
top="220" left="180"
click="bnLogIn_clickHandler(event)"/>
</s:Panel>
The Comp2 looks like
<fx:Script>
<![CDATA[
import events.LoginEventComplete;
import valueObjects.Employee;
protected function init():void
{
this.addEventListener(LoginEventComplete.LOGIN, mainHandler);
}
private function mainHandler(event:LoginEventComplete):void{
tiWorkEmail.text = event.workemail;
}
]]>
</fx:Script>
<fx:Declarations>
</fx:Declarations>
<s:layout>
<s:VerticalLayout verticalAlign="middle" horizontalAlign="center"/>
</s:layout>
<s:Label id="idWorkEmail" text="Email"/>
<s:TextInput id="tiWorkEmail"/>
The custom event LoginEventComplete look like
package events
{
import flash.events.Event;
public class LoginEventComplete extends Event
{
public var workemail:String;
public static const LOGIN:String = "login";
public function LoginEventComplete(type:String)
{
super(type, true, false);
this.workemail = workemail;
}
override public function clone():Event
{
var newEvent:LoginEventComplete = new LoginEventComplete(type);
newEvent.workemail = workemail;
return newEvent;
}
}
}
Can someone help please? Many thanks!
Best David W
You're realizing one of the problems that any hierarchical view structure has to pass information. This problem can be solved in severals ways, but I feel that I should tell you that you might need to look into an application framework if your application is going to get large. One like Parsley or RobotLegs.
Now, onto a solution. You won't be able to use bubbling to go from one sibling component to another, since bubbling goes up the display list (child to parent and so on). You can either listen for the event on the common parent and then push the information into the other sibling, but that's adding a lot of logic on the parent container.
An easy fix would be to create a model (a class that just has public properties to store information) that is bindable on the parent and binded into both siblings. Then if one changes the information, the other can receive that same information (or use bindings).
For example:
<fx:Script>
<![CDATA[
import events.LoginEventComplete;
[Bindable] private var someData:SomeModel = new SomeModel();
protected function loginHandler(event:LoginEventComplete):void
{
myViewStack.selectedChild = idViewMain;
}
]]>
</fx:Script>
<s:Panel width="100%" height="100%">
<mx:ViewStack id="myViewStack" selectedIndex="0" width="100%" height="100%">
<views:Comp1 id="idViewLogin" login="loginHandler(event)" data="{someData}/>
<views:Comp2 id="idViewMain" data="{someData}/>
</mx:ViewStack>
</s:Panel>
I can tell you that dispatched events only move up the hierarchy chain. It sounds like, based on your first code segment, you are trying to get it to move sideways.
<mx:ViewStack id="myViewStack" selectedIndex="0" width="100%" height="100%">
<views:Comp1 id="idViewLogin" login="loginHandler(event)"/>
<views:Comp2 id="idViewMain"/>
</mx:ViewStack>
So, one solution is to have a listener in the main component for the event which then executes a public method in the comp2. It is probably the easiest route.
Another solution may be to store the value in a global object; which both components have references to.
I have created a Viewstack and using a Tile component and repeating LinkButtons I was able to make a multi column navigation with the viewstack as the dataprovider. My question is can this be done better? My code is below and I am wondering if I took the long way around this approach.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:BasicLayout />
</s:layout>
<fx:Script>
<![CDATA[
import mx.controls.Alert;
private var _listItem:Object;
private var n:int=0;
public function get listItem():Object
{
return this._listItem;
}
public function set listItem(listItem:Object):void
{
try{n++;
this.changeSelection(this._listItem);
}catch(e:Error){}
if(n==1 || n > this.viewStack.length){
this._listItem = listItem;
this.changeSelection(listItem);
}
}
private function setSelection(obj:Object):void{
this.viewStack.selectedIndex = this.viewStack.getChildIndex(this.viewStack.getChildByName(obj.target.getRepeaterItem().name));
this.listItem = obj.target;
}
private function checkSelection(obj:Object):void{
if(obj.target.getRepeaterItem() == this.viewStack.selectedChild){
if(this.listItem != obj.target){
this.listItem = obj.target;
}
}
}
private function changeSelection(obj:Object):void{
if(obj.getRepeaterItem() == this.viewStack.selectedChild){
obj.setStyle("color","#000000");
}else{
obj.setStyle("color","#999999");
}
}
]]>
</fx:Script>
<mx:Tile id="tiles" horizontalGap="20" verticalGap="0" y="210" direction="vertical">
<mx:Repeater id="masterList" dataProvider="{viewStack}">
<mx:LinkButton
id="btn"
label="{masterList.currentItem.label}"
click="this.setSelection(event)"
color="#999999"
creationComplete="checkSelection(event);" />
</mx:Repeater>
</mx:Tile>
<mx:ViewStack id="viewStack" height="200" width="300" backgroundColor="#000000" >
<mx:VBox id="vb1" backgroundColor="#FF0000" label="Screen One"/>
<mx:VBox id="vb2" backgroundColor="#00FF00" label="Screen Two"/>
<mx:VBox id="vb3" backgroundColor="#0000FF" label="Screen Three"/>
<mx:VBox id="vb4" backgroundColor="#00FFFF" label="Screen Four"/>
</mx:ViewStack>
</s:Application>
Looks to me like you've got navigation links that expose different and that those links change color based on which one is selected. Assuming that's the case, it sounds an awful lot like a tab-based navigation model. My approach would be to use the spark TabBar and skin the tabs to look like links. That way you can get rid of most of your code and let the tab skin handle changing the colors based on their current state. Also, you wouldn't need any of the code you have for changing the view stack since the TabBar would handle that for you. Hope that helps.
I have a Renderer:
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Metadata>
[Event(name="addToCart",type="event.ProductEvent")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import events.ProductEvent;
import mx.collections.ArrayCollection;
protected function button1_clickHandler(event:MouseEvent):void
{
var eventObj:ProductEvent=new ProductEvent("addToCart",data.price,data.descript);
dispatchEvent(eventObj);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:states>
<s:State name="normal"/>
<s:State name="hovered"/>
</s:states>
<s:BorderContainer >
<s:layout>
<s:VerticalLayout paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"/>
</s:layout>
<s:Label text="{data.descript}"/>
<mx:Image source="{data.url}" width="50" height="50" width.hovered="100" height.hovered="100"/>
<s:Label text="{data.price}"/>
<s:Button includeIn="hovered" click="button1_clickHandler(event)" label="buy"/>
</s:BorderContainer>
</s:ItemRenderer>
and the custom event class:
package events
{
import flash.events.Event;
[Bindable]
public class ProductEvent extends Event
{
public var price:String;
public var descript:String;
public function ProductEvent(type:String,price:String, descript:String)
{
super(type);
this.price=price;
this.descript=descript;
}
override public function clone():Event
{
return new ProductEvent(type,price,descript);
}
}
}
but i cannot call that event in a container from the main application
<s:SkinnableDataContainer id="Sk" x="200" y="300" left="100" right="900" dataProvider="{imagesCollection}" itemRenderer="components.ImageRenderer" includeIn="normal" >
<s:layout>
<s:TileLayout/>
</s:layout>
</s:SkinnableDataContainer>
any ideas?
thanks
I want to make this:
<s:SkinnableDataContainer id="Sk" x="200" y="300" left="100" right="900" dataProvider="{imagesCollection}" itemRenderer="components.ImageRenderer" includeIn="normal" ***addToCart=something(event)*** >
Events are not called, so I'm not entirely sure what you want.
You can create an instance of an event class, like this:
var myProductEvent : ProductEvent = new ProductEvent("productEventTypeA", true, ...); // true is for enabling Bubbles, so that the event bubbles up to the listener.
You can dispatch that event from an itemRenderer the same way you would do so if you were using an event elsewhere:
dispatchEvent(myEvent);
Also on the item renderer, declare the event that is going to be dispatched:
[Event(name="productEventTypeA", type="local.events.ProductEvent")]
You can add an event listener on the List or DataGroup component implementing your item renderer so it runs your code after the event is dispatched:
myList.addEventListener("productEventTypeA", onProductEvent); // or a constant instead of "productEventTypeA"
or
myDataGroup.addEventListener("productEventTypeA", onProductEvent); // or a constant instead of "productEventTypeA"
And finally declare your listener function in the same file you added the event listener:
public function onProductEvent(e:ProductEvent):void
{
// do stuff
}
Note: In an itemRenderer, you often want to make your event bubble so that it can be listened to on the component which uses renderers--usually a list based class.
I hope this helps someone, you don't need to do all that...
It's very simple:
ItemRenderer:
protected function dispatchMyCustomEvent():void {
(owner as List).dispatchEvent(new CustomEvent);
}
if the parent isn't a list then just typecast it to what the parent is.
then in your component that has the list you can do the following:
protected function creationComplete():void {
myList.addEventListener(CustomEvent.TYPE, onMyCustomeEvent);
}
protected function onMyCustomEvent(event:MyCustomEvent):void {
// handle the event
}
It's hard to make out what you're trying to do from the question, because of the formatting, and it appears there's some text missing.
However. try making the event bubble, and add the event listener on the list which holds the itemRenderers.
Eg:
<mx:Canvas creationComplete="list1.addEventListener('addToCart',onAddToCart)">
<mx:List id="list1" itemRenderer="com.foo.YourItemRenderer" />
</mx:Canvas>
<!-- YourItemRenderer.mxml -->
<mx:Canvas>
<!-- Other item renderer stuff -->
<mx:Button click="dispatchEvent(new ProductEvent('addToCart'))" />
</mx:Canvas>
// ProductEvent.as
public class ProductEvent {
public function ProductEvent(type:String,bubbles:Boolean=true) {
super(type,bubbles);
// ... etc
}
}
Perhaps this is a little out of scope, but that sort of signaling with events is where MVC frameworks like Cairngorm shine. I usually think of them as AS event buses.
This is possible using the Event metadata tag: http://livedocs.adobe.com/flex/3/html/help.html?content=createevents_3.html
You'll have to sub-class SkinnableDataContainer and add the metadata tag:
[Event(name="addToCart", type="events.ProductEvent")]
public class MySkinnableDataContainer extends spark.components.SkinnableDataContainer
{
}
You can also do it in MXML:
<?xml version="1.0"?>
<!-- ../MySkinnableDataContainer.mxml -->
<s:SkinnableDataContainer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark">
<mx:Script>
<![CDATA[
import events.ProductEvent;
]]>
</mx:Script>
<mx:Metadata>
[Event(name="addToCart", type="events.ProductEvent")]
</mx:Metadata>
</s:SkinnableDataContainer>
Then use this new class instead of the SkinnableDataContainer:
<MySkinnableDataContainer id="Sk" x="200" y="300" left="100" right="900" dataProvider="{imagesCollection}" itemRenderer="components.ImageRenderer" includeIn="normal" addToCart="something(event)" />
Note: depending on your events bubble property you may have to catch it and then re-forward it along inside of MySkinnableDataContainer.
Make the event bubble:
public function ProductEvent(type:String,price:String, descript:String)
{
super(type, true); // Add bubbles = true instead of default false
this.price=price;
this.descript=descript;
}
Then in your app:
<s:SkinnableDataContainer id="Sk" x="200" y="300" left="100" right="900" dataProvider="{imagesCollection}" itemRenderer="components.ImageRenderer" includeIn="normal" creationComplete="Sk.addEventListener('addToCart', myFunctionToHandle)">
Add an event listener on creation complete.