Create dropdownlist with delete button in this itemrenderer - actionscript-3

I works with Flex 4.5 and I like to create a custom dropdownlist. Indeed, I'd like to show in each line on my dropdownlist a label and a delete button.
The goal is to delete the line on click to delete button.
This look like simple, but I don't found how to do that.
Thanks for helping

You have to jump through a few hoops for this one because DropDownList prevents any MouseEvent.CLICK from an object inside an ItemRenderer from being fired.
First things first: you will need a custom event for this to work. One that carries your item or at least its index. e.g.:
public class ItemEvent extends Event {
public static const REMOVE:String = "itemRemove";
public var item:MyClass;
public function ItemEvent(type:String, item:MyClass,
bubbles:Boolean=false,
cancelable:Boolean=false) {
super(type, bubbles, cancelable);
this.item = item;
}
override public function clone():Event {
return new ItemEvent(type, item, bubbles, cancelable);
}
}
Then you create a custom ItemRenderer with a 'delete' Button that will dispatch this event.
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
private function remove():void {
owner.dispatchEvent(
new ItemEvent(ItemEvent.REMOVE, data as MyClass)
);
}
]]>
</fx:Script>
<s:Label id="labelDisplay" verticalCenter="0" left="10" />
<s:Button verticalCenter="0" right="10" width="16" height="16"
mouseDown="remove()" />
</s:ItemRenderer>
Important here is that you catch the MOUSE_DOWN event of the Button, since its CLICK event doesn't fire (as mentioned before). The owner property of the ItemRenderer refers to the List it is a child of.
Now the last piece of the puzzle. Here's your DropDownList with custom ItemRenderer:
<s:DropDownList id="myDropDownList" dataProvider="{dp}"
itemRenderer="MyItemRenderer" />
And here's how you listen for that custom event and remove the selected item:
myDropDownList.addEventListener(ItemEvent.REMOVE, removeSelectedItem);
private function removeSelectedItem(event:ItemEvent):void {
var items:IList = myDropDownList.dataProvider;
var index:int = items.getItemIndex(event.item);
items.removeItemAt(index);
}
Because we caught the MOUSE_DOWN instead of CLICK the myDropDownList.selectedIndex property will still be at the previously selected item (or -1 if none was selected). This is why we needed the custom event, because there was no other way of knowing which is the item you want to remove.

Related

DataGrid ItemRenderer

Here's the thing: I've this DataGrid that holds a column with the purpose of handle some actions. So, this DataGrid have several data columns and, at the end there is this special column.
At this special column named "operations" there are two icons; one of 'em shows an alarm icon (like a little bell). So, what I've to accomplish is that, initially the alarm icon shows invisible; when the user sets an alarm (through another interface) the alarm icon shows up (with default style color) and when it's time to fire the alarm the alarm icon is supposed to take another style color (like yellow).
So, I've this next definition:
<mx:DataGrid id="dgSomeValues"
dragEnabled="true"
draggableColumns="false"
width="100%" height="100%"
horizontalScrollPolicy="off"
resizableColumns="true"
rowHeight="19">
<components:columns>
<mx:DataGridColumn id="dgcItem" headerText="{resourceManager.getString('resources','columnItem')}" width="70" resizable="false"/>
<!--
Some other dataGridColumns
-->
<mx:DataGridColumn id="dgcOperation" headerText=" " width="50" resizable="false">
<mx:itemRenderer>
<fx:Component>
<components:OperationItemRenderer/>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</components:columns>
And definition for OperationItemRenderer is like this:
import flash.events.MouseEvent;
import com.broker.control.BrokerEvent;
import mx.containers.HBox;
public class OperationItemRenderer extends HBox
{
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Alarm Button --> bell
**/
private var btnAlarm:Icon;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor
**/
public function OperationItemRenderer()
{
super();
this.setStyle("horizontalGap",0);
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* #inheritDoc
**/
override public function set data(value:Object):void
{
super.data = value;
}
/**
* #inheritDoc
**/
protected override function createChildren():void
{
super.createChildren();
if (!btnAlarm){
btnAlarm = new Icon();
btnAlarm.styleName = ""; // Initially do not shows any icon.
btnAlarm.enabled = true;
btnAlarm.addEventListener(MouseEvent.CLICK, btnAlarmClickHandler,false,0,true);
addChild(btnAlarma);
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (btnAlarm){
btnAlarm.width = unscaledWidth/2;
btnAlarm.height= unscaledHeight;
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* If this item has an alarm, then it will show an icon.
* States for alarm icon are: Default --> icnAlarmOff
* Fired --> icnAlarm
*/
public function upgradeIcon(toogle:Boolean):void
{
btnAlarm.styleName = toogle ? "icnAlarm" : "icnAlarmOff";
}
//--------------------------------------------------------------------------
//
// Event Handlers
//
//--------------------------------------------------------------------------
protected function btnAlarmaClickHandler(event:MouseEvent):void
{
if (btnAlarm.styleName == "favIcnAlarma") {
var evt:BrokerEvent;
evt = new BrokerEvent(BrokerEvent.SETUP_ALARM);
dispatchEvent(evt);
}
}
}
The function "upgradeIcon" it's supposed to be called from another section in the application where a rowFunction it's called everytime the DataGrid's datasource is refreshed. At this rowFunction I want to find a way to access DataGrid ItemRenderer so that way I'll be able to call this upgradeIcon function.
The question is, how can I access DataGrid's ItemRenderer programatically? What I already tried is something like this:
var c:ClassFactory = view.dgcOperation.itemRenderer as ClassFactory;
if (c != null && c.generator != null) { // c never is null at this point
var f:OperationItemRenderer = c.generator as OperationItemRenderer;
f.upgradeIcon(change); // f it's always null
//(c.generator as OperationItemRenderer).upgradeIcon(change);
}
But this approach does not work. Any help would be greatfully appreciated.
Thanks in advance!
try to dispatch an event when user set alarm...and add a listener of this event in your renderer, so when a new alarm is set, an event will be dispatched from your alarm interface, and be captured in your renderer which will update your icon style.
Your datagrid must be having a data provider of value objects (say Report is name of your class) . If you have marked your Report class bindable then any change in any of its property will dispatch PropertyChangeEvent and then you can override set Data method in the item-renderer and based on the value of that property you should have taken necessary action.
ItemRenderers are recycled and fetching them in this was is a bad practice. You should not alter an ItemRenderer directly rather alter the underlying object.

Tabbing does not work in mx:TileList component

I am using mx:Tilelist component to display set of textfields on screen, but when i try to traverse the fields through TAB foucs move out of the list. Please provide solution for this problem. Following is the code i am using
<mx:TileList id="tileList"
dataProvider="{collection}"
change="setCurrentIndex(tileList.selectedIndex);"
dataChange="setCurrentIndex(tileList.selectedIndex);"
columnCount="1"
columnWidth="345"
itemRenderer="components.InputParamIR"
rowHeight="30"
verticalScrollPolicy="auto"
horizontalScrollPolicy="auto"
backgroundColor="#EEEEEE"
dragEnabled="false"
dragMoveEnabled="true"
dropEnabled="true"
width="100%" height="100%"
itemClick="chartTileClick(event);"
/>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.containers.Panel;
[Bindable]
public var index:uint;
[Bindable]
public var collection:ArrayCollection = new ArrayCollection();
[Bindable]
public var isVisible:Boolean ;
public function initEventsLocal(event:Event):void
{
this.initEvents(event);
collection = new ArrayCollection();
isVisible = false;
}
private function chartTileClick(event:ListEvent):void
{
event.currentTarget.tabFocusEnabled=true;
event.currentTarget.tabEnabled=true;
}
]]>
</fx:Script>
In Flex List Itemrenderer may not get the focus since it is non editable and it will not implement the focus manager interface, you need to implement IFocusManagerComponent in your item renderer components.InputParamIR also you have to override your TileList class to enable tab for childrens.
package
{
import mx.controls.TileList;
public class MyTileList extends TileList
{
override protected function createChildren():void {
super.createChildren();
this.listContent.tabChildren = this.tabChildren
this.listContent.tabEnabled = this.tabEnabled
}
}
}
Have a look in to these
Tile list item renderer text focus solved
Issues with keyboard navigation on list with custom renderer
i hope this will help you
happy coding...

Receiving Error 1026 in AS3 and do not know why

I am getting error 1026 which is "Constructor functions must be instance methods" and I don't know why. I am creating a media player type program and am having trouble with my music portion. I made a function music player before this and copied the code over to my new project. The only thing that is different now is that the code is in a state and not the main.mxml file. This is my code below and I am receiving the error where it says "public var music:Sound;" I don't see why I am getting this error and any help would be appreciated!
<fx:Declarations>
<fx:XML id="musicXML" source="data/musak.xml" />
<s:XMLListCollection id="musicCollection" source="{musicXML.song}" />
</fx:Declarations>
<s:HGroup>
<s:DataGrid id="musicGrid" dataProvider="{musicCollection}" click="onClick()" />
<local:MusicPosition id="mProgress" width="319" height="83"/>
<s:VGroup height="55">
<s:Label text="Now playing: {musicGrid.selectedItem.#name}" />
<s:Label id="txtPosition" width="91"/>
</s:VGroup>
</s:HGroup>
<s:Button x="146" y="93" label="play" click="playMusic()"/>
<s:Button x="270" y="93" label="pause" click="pauseMusic()"/>
<fx:Script>
<![CDATA[
//set your variables
public var music:Sound;
[Bindable]public var musicChannel:SoundChannel;
public var pausedTime:Number = 0;
public function onClick():void {
if(musicChannel) {
musicChannel.stop();
//clean up the variables
music = null;
musicChannel = null;
}
music = new Sound();
music.load(new URLRequest(musicGrid.selectedItem.file.toString()));
music.addEventListener(Event.COMPLETE, onMusicLoad);
}
public function onMusicLoad(e:Event):void {
mProgress.update(0);
//new channel
musicChannel = music.play();
}
protected function onE(e:Event):void {
if(musicChannel) {
txtPosition.text = millisecondsToSeconds(musicChannel.position).toString() + "/" +
millisecondsToSeconds(music.length).toString();
mProgress.update(musicChannel.position/music.length);
mProgress.alpha = 1;
} else
mProgress.alpha = 0;
}
protected function millisecondsToSeconds(milliseconds:Number):Number {
return Math.round(milliseconds/1000);
}
public function pauseMusic():void {
if(musicChannel) {
pausedTime = musicChannel.position;
musicChannel.stop();
}
}
public function playMusic():void {
if(musicChannel) {
musicChannel = music.play(pausedTime);
}
}
]]>
</fx:Script>
Be very careful when naming thing. You cannot create a variable, or method, inside your class that is the same name as the class itself.
A common--but not required--naming convention is this:
Use CamelCase for classes. In this case, your class would be named either Music.mxml or music.as. Based on your code, it seems you already follow this convention with your MusicPosition class.
Use camelcase for method, but make the first letter lowercase. Your variable could be named music in this case. Or, you may create a variable named musicPosition. Remember in Flex, the id attribute of an MXML tag is the equivalent of a variable name.
For constants, use all uppercase. Constants are commonly used as event types.
Once, again, these are common conventions [in programming languages beyond Flex/ActionSCript] but are not required or enforced by the Flex SDK.

How to handle a change in a custom GridItemRenderer

I'm using a DataGrid in one of my project, with a custom ItemRenderer for one of the column to represent a boolean value as a CheckBox
<s:DataGrid id="clients" resizableColumns="false">
<s:columns>
<s:ArrayList>
<s:GridColumn dataField="fullName" headerText="Client name" />
<s:GridColumn dataField="active" headerText="Active?" width="90"
itemRenderer="CheckBoxGridItemRenderer"/>
</s:ArrayList>
</s:columns>
</s:DataGrid>
And here is the code of CheckBoxGridItemRenderer:
<?xml version="1.0" encoding="utf-8"?>
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
clipAndEnableScrolling="true">
<fx:Script>
<![CDATA[
override public function prepare(hasBeenRecycled:Boolean):void {
checkBox.selected = Boolean(data[column.dataField]);
}
]]>
</fx:Script>
<s:CheckBox id="checkBox" horizontalCenter="0" />
</s:GridItemRenderer>
Now here is my question: What is the best way to handle the change in data from the class I declared the DataGrid ?
I tried to change the data value from within my custom item renderer class:
private function valueChange():void
{
data.active = checkBox.selected;
}
[...]
<s:CheckBox id="checkBox" change="valueChange()" />
and then listen to CollectionEvent.COLLECTION_CHANGE event on the dataProvider of the DataGrid but I never handle any change. Any thoughts or advices?
Where do you change the data. When an element of data collection changed, the DataGrid can detect nothing unless you dispatch CollectionEvent.COLLECTION_CHANGE for the data collection.
In addition, the data collection should be something like ArrayCollection, not Array.
As I found a solution to my problem, I will post it here, but I leave the question open for some time in case someone come up with a better one.
First I created a CustomEvent (I could use the CollectionEvent class but this way I'm sure I don't mess up with the internal logic of the DataGrid)
package
{
import flash.events.Event;
public class CustomEvent extends Event
{
public static const CHANGE:String = "change";
public var column:String;
public var data:Object;
public function CustomEvent(column:String, data:Object)
{
super(CHANGE, true);
this.column = column;
this.data = data;
}
}
}
Notice the call to super() with bubbles set to true. This is actually needed for the event to be handle here.
Then I modified my CheckBoxGridItemRenderer to dispatch this event when the checkbox is selected/deselected:
<?xml version="1.0" encoding="utf-8"?>
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" clipAndEnableScrolling="true">
<fx:Script>
<![CDATA[
import CustomEvent;
override public function prepare(hasBeenRecycled:Boolean):void {
if (data)
{
checkBox.selected = Boolean(data[column.dataField]);
}
}
private function valueChange():void
{
data[column.dataField] = checkBox.selected;
dispatchEvent(new CustomEvent(column.dataField, data));
}
]]>
</fx:Script>
<s:CheckBox id="checkBox" horizontalCenter="0" change="valueChange()" />
</s:GridItemRenderer>
Finally I only have to listen to my custom event dispatched by the DataGrid instance:
clients.addEventListener(CustomEvent.CHANGE, handleCustomEvent);

Flex3: Custom Item Renderer does not listen to events dispatched by parent

I have a List with a custom ItemRenderer. The ItemRenderer contains a Checkbox and a Label.
The component with the List has a 'select all' checkbox.
When the 'select all' checkbox is checked, it dispatches an event that each item should listen to in order to select its own checkbox.
The eventlistener is added on creationComplete of each item, and the event is dispatched correctly when the 'select all' checkbox is selected, but the listener in the custom ItemRenderer does not listen.
How do I make the ItemRenderer listen to an event that is dispatched in its parent??
I'll add some code examples to clarify:
------- container ----------
<mx:VBox>
<mx:Script>
<![CDATA[
public var user1 = new User(1, "Jack");
public var user2 = new User(2, "John");
public var user3 = new User(3, "Mary");
[Bindable]
public var users:ArrayCollection = new ArrayCollection([user1], [user2], [user3]);
public static const SELECT_ALL_USERS:String = "selectAllUsers";
private function selectAllChangeHandler():void
{
if (selectAll.selected)
dispatchEvent(new Event(SELECT_ALL_USERS,true));
}
]]>
</mx:Script>
<mx:CheckBox id="selectAll" change="{selectAllChangeHandler()}" />
<mx:List dataProvider="{users}" itemRenderer="myRenderer" />
</mx:VBox>
------- renderer ----------
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox creationComplete="{init()}">
<mx:Script>
<![CDATA[
private function init():void
{
addEventListener (Container.SELECT_ALL, selectAllHandler, false, 0, true);
}
private function selectAllHandler():void
{
checkbox.selected=true;
}
private function selected(id:int):Boolean
{
return id==1 || id==3;
}
]]>
</mx:Script>
<mx:CheckBox id="checkbox" selected="{selected(data.id)}" />
<mx:Label text="{data.name}" />
</mx:HBox>
Please note that the users ArrayCollection or its containing user objects cannot be changed because I need the values later on.
So when 'selectAll' is clicked, each checkbox in the list should also be checked.
Your custom ItemRenderers should not register an event listener with their parent, but with your "select all" checkbox, i.e.
theCheckbox.addEventListener(YourEvent.YOUR_EVENT, itemRendererSelectAllHandler);
Failing that, can you post your code which adds the event listener and which dispatches the event form the checkbox?
edit:
Here's your bug: In renderer's init(), you need to add an event listener not to the renderer, but to the container which dispatches the event. So make that a
container.addEventListener(Container.SELECT_ALL_USERS, selectAllHandler, false, 0, true);
Doing select all in flex is little complex but I will tell you the way we did it and it works great.
We created a List derived control called "ExList" which has a property "selectAllCB" which we bind it to existing check box which will work for select all logic. Please note, when we set selectAllCB property, we make ExList to listen to checkbox's events.
When checkbox is checked, we manually set selectedItems array to array of dataProvider and all items are selected.
Playing with itemRenderer doesnt work correctly because when you program your list again and again everytime you have to write so much of code.
I am attaching some sample code here below..
public class ExList extends List
{
public function ExTileList()
{
super();
this.allowMultipleSelection = true;
}
private var _selectAllCB:CheckBox = null;
[Bindable]
public function get selectAllCB():CheckBox
{
return _selectAllCB;
}
public function set selectAllCB(v:CheckBox):void
{
if(v==null)
return;
_selectAllCB = v;
v.addEventListener(ListEvent.ITEM_CLICK, handleAllChange,false, 0 , true);
v.addEventListener("change", handleAllChange,false, 0 , true);
}
private function handleAllChange(e:Event):void
{
if(_selectAllCB.selected)
{
this.selectedItems = this.dataProvider.toArray();
}
else
{
this.selectedItems = new Array();
}
}
}
And you can use it as...
<CheckBox id="sAll"/>
<ExList selectAllCB="{sAll}"/>
// Please note its in curly braces
This is how I achieved the solution:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" height="150" verticalGap="0">
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
import mx.controls.CheckBox;
import mx.collections.ArrayCollection;
public var listData:ArrayCollection;
private function selectAll():void
{
listChkBox.selectedItems = listData.toArray();
for each (var item:Object in listChkBox.selectedItems)
{
CheckBox(listChkBox.itemToItemRenderer(item)).selected = true;
}
}
private function resetAll():void
{
listChkBox.selectedItems = listData.toArray();
for each (var item:Object in listChkBox.selectedItems)
{
CheckBox(listChkBox.itemToItemRenderer(item)).selected = false;
}
}
]]>
</mx:Script>
<mx:List width="100%" height="100%" id="listChkBox" labelField="name" allowMultipleSelection="true"
dataProvider="{listData}" selectionColor="#FFFFFF" >
<mx:itemRenderer>
<mx:Component>
<mx:CheckBox >
</mx:CheckBox>
</mx:Component>
</mx:itemRenderer>
</mx:List>
<mx:HBox width="100%" backgroundColor="#E2DEDE" paddingBottom="2" paddingLeft="2" paddingTop="2" paddingRight="2" borderStyle="solid">
<mx:Button label="All" id="btnAll" click="selectAll()" />
<mx:Button label="None" click="resetAll()"/>
</mx:HBox>
</mx:VBox>
The simple solution is to use typed objects in the list of data you are presenting and then take advantage of databinding and the binding utils to capture changes to the underlying data property on the item renderer. Override the 'set data' function in the item renderer to do whatever you need when some property in the object gets changed to reflect the 'select all/de select all' state.