I have a List with TextInput as item renderer. I want to get the value entered in the TextInput (form the TextInputItemRenderer) and pass it the main application to do some checks(upon tapping enter on the textInput -- see code below).
I know that we can do it thru dispatching event but I still don't understand how to pass a variable from the ItemRenderer to the main app.
Help Pls.
Thanks
<?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"
autoDrawBackground="true" xmlns:components="components.*" width="100%"
>
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
override public function set data( value:Object ) : void {
super.data = value;
}
protected function myTextInput_enterHandler(event:FlexEvent):void
{
trace(myTextInput.text);
What Next??
}
]]>
</fx:Script>
<components:ClearableTextInput text="{data.label}" id="myTextInput" enter="myTextInput_enterHandler(event)"/>
</s:ItemRenderer>
i'm not sure if I got your question correctly but would this help?
http://www.ajibanda.blogspot.com/2011/02/changing-currentstate-of-main-and.html
Instead of trying to access from MainApp to itemRenderer, i think you can do backward. Follow one of two solutions below:
In itemRenderer, assign value you want to check later to a public global variable on the MainApp. The limitation is you then only enable to check it on MainApp, not any where esle (other itemRenderer, component, module etc.)
Use EvenBus to put the value to a global container. Create a static eventBus instance in AppUtils, for example. In itemRenderer, AppUtils.eventBus.dispatch() an event with the value attached to it each time the value changed. And then use AppUtils.eventBus again to addEventListener() to retrieve the value and check wherever you want. Google AS3Commons for EventBus.
Related
Is there a way to listen for this? I can easily listen for click which selects a RadioButton, but I can't seem to find a way to listen for when the RadioButton has been deselected.
Any ideas?
Thanks!
Back in the Flex 2 days, the "change" event triggered when a radio button was deselected. However, this little convenience disappeared in Flex 3, for some reason, and I don't believe that we were provided with any sort of replacement event.
Handling your events at the RadioButtonGroup level is all fine and well, except that there are times when you really want to handle the events on the radio button level -- particularly if you were hoping to interact with a data provider entry via an itemRenderer that is drawing the radio button.
Conveniently, I have a little bit of boilerplate code you can use as drop-in replacements for RadioButton and RadioButtonGroup that provide a "unselect" event at the radio button level. Here is the SmartRadioButton, first of all:
<?xml version="1.0" encoding="utf-8"?>
<s:RadioButton 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:Script>
<![CDATA[
import events.SmartRadioButtonEvent;
public function notifyDeselect():void {
dispatchEvent(new SmartRadioButtonEvent('deselect'));
}
]]>
</fx:Script>
<fx:Metadata>
[Event(name="deselect", type="events.SmartRadioButtonEvent")]
</fx:Metadata>
</s:RadioButton>
And here is the SmartRadioButtonGroup:
<?xml version="1.0" encoding="utf-8"?>
<s:RadioButtonGroup xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
change="selectionChanged();"
>
<fx:Script>
<![CDATA[
import spark.components.RadioButton;
import components.SmartRadioButton;
protected var oldSelection:SmartRadioButton = null;
// Notify our old selection that it has been deselected, and update
// the reference to the new selection.
public function selectionChanged():void {
var newSelection:SmartRadioButton = this.selection as SmartRadioButton;
if (oldSelection == newSelection) return;
if (oldSelection != null) {
oldSelection.notifyDeselect();
}
oldSelection = newSelection;
}
// Override some properties to make sure that we update oldSelection correctly,
// in the event of a programmatic selection change.
override public function set selectedValue(value:Object):void {
super.selectedValue = value;
oldSelection = super.selection as SmartRadioButton;
}
override public function set selection(value:RadioButton):void {
super.selection = value;
oldSelection = super.selection as SmartRadioButton;
}
]]>
</fx:Script>
</s:RadioButtonGroup>
The two property overrides are in there to make sure that we correctly update the oldSelection, in the event of a programmatic change to the group's selection.
SmartRadioButtonEvent isn't anything fancy or important. It could probably just be a plain Event, since there isn't a special payload.
I have tested the above code, and it all works, but there are surely edge conditions, and other oddities that should be addressed, if it's used in a larger system.
I have a CustomDataGrid that extends from DataGrid and CustomDataGridColumn that extends from DataGridColumn.
CustomDataGridColumn has member variables of type Function.
Inside my view, I inject a presentation model using parsley.
The code is as follows:
<fx:Declarations>
<spicefactory:Configure/>
</fx:Declarations>
<fx:Script>
[Inject(id="associatedDocumentsPM")]
[Bindable]
public var model:AssociatedDocumentsPM;
</fx:Script>
<customDataGrid:CustomDataGrid id="AssocDocGrid"
width="100%" height="{(documentDataList.length+2)*20}"
doubleClickEnabled="true" enabled="{modeHandler.appEnable}"
dataP="{documentDataList}"
sortableColumns="false">
<customDataGrid:columnList>
<customDataGrid:CustomDataGridColumn
textAlign="left"
dataFieldIdentifier="documentName"
headerText="Document Type"
modifyLabelField="{model.modifyLabelField}"
dataField="documentName"
isNaNZero="true"
showDataTips="true"
editable="false"/>
...more columns here...
</customDataGrid:columnList>
</customDataGrid:CustomDataGrid>
The AssociatedDocumentsPM has functions defined and these are set in the columns.
One example being for attribute modifyLabelField="{model.modifyLabelField}"
CustomDataGridColumn.myLabelField is of type Function. myLabelField inside AssociatedDocumentsPM is a public function.
The Parsley Context file is in the parent of the above file and declares the PM as follows:
AssocDocPMFactory is a class with a sole function decorated with [Factory].
So the problem is the following:
When I debug the application and check the columnList of the DataGrid, the variable modifyLabelField is null.
Are function bindings treated differently than variables? I'm using Flex 4.5.1 together with Parsley 2.4.1
I understand that injection could happen after creationComplete is invoked but I thought the binding would take care of that.
I have a feeling that the model - the PM - is null until much much later and the function binding is not triggered.
I tried to use FastInject as well but to no avail.
Is this a problem with function pointers and Flex binding?
No it isn't. If you have these kind of doubts, it's always a good idea to quickly set up a simple isolated test situation that verifies your assumption. I created the following to test yours:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
creationComplete="onCreationComplete()" >
<fx:Script>
<![CDATA[
private function onCreationComplete():void {
test = function(item:*):String {
return "AAA";
}
}
[Bindable]
private var test:Function;
]]>
</fx:Script>
<s:List labelFunction="{test}">
<s:dataProvider>
<s:ArrayList>
<fx:String>A</fx:String>
<fx:String>B</fx:String>
<fx:String>C</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:List>
</s:Application>
If the test Function variable is declared Bindable, you'll see 3 times "AAA". If you remove the Bindable metadata, you'll see "A", "B", "C".
So clearly binding works with function pointers too (and you'll have to look elsewhere to find your nullpointer).
I have a custom Flex 4+ component that I am trying to make and have the skin be aware of changes to a custom property. This property will determine the graphic on the button (and some other visual changes) but the data will change constantly as it will be updated by a timer.
I've looked at untold examples and still seem unable to get the syntax correct or discover how things should be separated. I've looked at overriding commitProperties and the PropertyChangeEvent without success. So I have two questions.
1) How can I get a skin to be notified of a bound property when it changes?
2) If the data for a bound property of the component is an object, will binding work properly if a property of the object changes (or would it be better to pass each property separately)?
Here is a stripped down example of what I'm trying to achieve.
The component looks like this:
<s:ButtonBase 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:Script>
<![CDATA[
private var _iconData:String;
[Bindable]
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
}
]]>
</fx:Script>
I'm calling it like this:
<components:MyButton id="myButton" iconData="{myData.curIconTag}" skinClass="skins.MyButtonSkin" />
I have a lot of different images I could be loading and so I'm afraid the number of states (with the combinations of up/down/over/disabled, etc. may get out of hand so the SetIconDisplay is setting the icon, but the real key is that I have other code in that function that needs to execute when the iconData property changes every X minutes or so. So the skin is something like this:
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
creationComplete="init()">
<fx:Metadata>
[HostComponent("components.MyButton")]
</fx:Metadata>
<s:states>
<s:State name="default" />
<s:State name="down"/>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="disabled" />
</s:states>
<fx:Script>
<![CDATA[
import components.MyButton;
[Embed(source="images/image1.png")]
private var icon1:Class;
[Embed(source="images/image2.png")]
private var icon2:Class;
[Embed(source="images/image3.png")]
private var icon3:Class;
[Bindable]
public var hostComponent:MyButton;
[Bindable]
private var iconClass:Class;
private function init():void
{
iconClass = new Class();
}
// how do I get this called when the iconData property on my custom component is changed?
private function SetIconDisplay():void
{
switch (hostComponent.iconData)
{
case "apple":
iconClass=icon1;
break;
case "orange":
iconClass=icon2;
break;
case "grape":
iconClass=icon3;
break;
}
}
]]>
</fx:Script>
<s:BitmapImage source="{iconClass}" x="0" y="0" width="180" height="108"/>
Again, don't worry as much about how the skin is actually doing what it is doing as that will probably change (not using states). I'm just trying to figure out how to call a specific function when the bound property is changed.
Thank You!
I ended up dispatching a custom event when the data is updated and listen for it in the skin.
The component:
<s:ButtonBase 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:Script>
<![CDATA[
import classes.CustomEvent;
private var _iconData:String;
[Bindable]
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
dispatchEvent(new CustomEvent("iconDataUpdated"));
}
]]>
</fx:Script>
The skin adds this:
protected function skin_preinitializeHandler(event:FlexEvent):void
{
hostComponent.addEventListener(CustomEvent.ICON_DATA_UPDATED,SetIconDisplay);
}
Having the base class call a function on one particular skin can get awkward, as it means that the base class is dependent on the skin class, which makes it difficult to swap out skins. There are two good ways to get around this:
Option 1: Move the iconClass up into the component. The skin class can bind directly to the property, and the logic for deciding which icon to use can be handled by the component instead of the skin. This keeps logic out of the skin, and keeps the amount of skinning code you have to work with down.
Option 2: Add an iconData property to the skin, and bind it to the iconData property of the host component. In the setter function, call SetIconDisplay when you have a valid value. This keeps the icons encapsulated in the skin, which may help if you want to use a very different skin for the same component.
Edit: If you're planning on creating several other skins that don't use the icons, #2 is the way to go. Create a property on the skin like so:
private var _iconData:String;
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
SetIconDisplay()
}
Then use a binding to connect it to the hostComponent:
<fx:Binding source="hostComponent.iconData" destination="iconData" />
Another solution to the general question, though maybe not ideal in this situation, is to call skin.invalidateDisplayList() whenever a property changes. Then, in the skin, override the updateDisplayList function and from there call a function that reacts to the changed properties, as well as calling the function on the parent class obviously.
See here: https://forums.adobe.com/thread/797247
<s:BitmapImage source="{hostComponent.iconClass}" />
should work
you don't need to declare public var hostComponent:MyButton;
it's part of SparkSkin
I have a List which has TextInput as itemRenderers for all its items. Upon application launch the items are rendered in the TextInputs correctly. The data is being populated from an Array of Objects.
What I want is, after the data has been populated in the ItemRenderers, I want to have an additional item renderer (TextInput of course)...so that if the user wants to enter another item, he can put it in the additional textInput.
And I also want to add the additional itemRenderer each time the user has added a new item and taps ENTER on the newly added item.
Below is my itemRenderer, there is the clearTxt_enterHandler handler..but I wonder how to add another itemRenderer upon "Enter".
Can somebody guide me with this?
Thx
<?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"
autoDrawBackground="true" xmlns:components="components.*" width="100%">
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<fx:Script>http://stackoverflow.com/questions/4199373/flex-4-is-a-good-practice-store-the-loaded-bitmapdata-in-a-value-object-and-then
<![CDATA[
import mx.events.FlexEvent;
import skins.ClearableTextInputSkin;
override public function set data( value:Object ) : void {
super.data = value;
//clearTxt.text = value.label;
}
protected function clearTxt_enterHandler(event:FlexEvent):void
{
trace("On Enter");
}
]]>
</fx:Script>
<components:ClearableTextInput text="{data.label}" id="clearTxt"
skinClass="skins.ClearableTextInputSkin" enter="clearTxt_enterHandler(event)" left="10" top="36" width="220" />
</s:ItemRenderer>
And this is my list that comes from the main application:
<s:List id="myList" itemRenderer="renderers.TextInputRenderer" dataProvider="{xxx}" width="100%">
<s:layout>
<s:TileLayout requestedRowCount="2"columnAlign="justifyUsingWidth"/>
</s:layout>
</s:List>
From the ItemRenderer, dispatch an Event to request the new ItemRenderer. Be sure to make it bubbling, so that it goes all the way up to the List that owns the ItemRenderer.
protected function clearTxt_enterHandler(event:FlexEvent):void
{
dispatchEvent(new Event("myCustomRequestEvent", true));
//replace with a real custom Event; this is for brevity
}
Listen for that event on the List component and in the handler just add a new element to its dataprovider (probably with an empty label).
myList.addEventListener("myCustomRequestEvent", addRow);
private function addRow(event:Event):void {
myList.dataProvider.addItem({label: null});
//replace anonymous object with your class
}
This will add a new item to the List. In your case you may have to bind requestRowCount to the number of items in the List, so that it will grow when you add the items.
<s:VerticalLayout requestedRowCount="{myList.dataprovider.length}" />
BTW: why are you using TileLayout? If what you want is two input boxes per item, you should put two TextInputs in one ItemRenderer and use VerticalLayout. It will make your life a lot easier.
I have following code.
<s:DropDownList dataProvider={_dataProvider}/>
<fx:Script>
private var _dataProvider:ArrayCollection = new ArrayCollection([{label:"one", data:1}, {label:"two", data:2}]);
</fx:Script>
I want to bind the data property of the selectedItem in the DropDownList. Is there a way to do this?
Not sure if this another approach to the question... but I created a custom dropdownlist and binded the selectedItem to incoming changes. When the value of my desired data changes it will trigger the dropdownlist to change its selection.
DropDownListBindable.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:DropDownList 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:Script>
<![CDATA[
import mx.controls.Alert;
[Bindable] public var valueField:String = "";
override public function set selectedItem(value:*):void{
try{
for(var i:uint=0;i<this.dataProvider.length;i++){
if(this.dataProvider[i][this.valueField]==value){
this.selectedIndex=i;
break;
}else{
this.selectedIndex=-1;
}
}
}catch(e:Error){}
}
]]>
</fx:Script>
</s:DropDownList>
On the application I import the custom dropdownlist and bind the valuefield with whatever needs to be binded... in your case its 'data'. I also created an object called 'mydata' for the dropdownlist to listen to for changes. When mydata changes the list will too. I've added a button to demonstrate how the list changes.
main.mxml
<?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"
xmlns:components="com.components.*">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] private var myData:Object = new Object();
[Bindable] private var _dataProvider:ArrayCollection = new ArrayCollection([{label:"one", data:1}, {label:"two", data:2}]);
]]>
</fx:Script>
<s:HGroup>
<components:DropDownListBindable
dataProvider="{_dataProvider}"
prompt="--select one--"
selectedItem="{this.myData}"
id="ddl"
valueField="data"
labelField="label"/>
<s:Button label="change datafield" click="this.myData=1"/>
</s:HGroup>
</s:Application>
I'm pretty sure the answer is no, but just to be clear; I'm confused.
If your dataProvider contains objects like this:
{label:"one", data:1}
First off, this syntax will create a generic object with no customization. If none of the properties on that object are explicitly defined, none of them can implement the Bindable metadata tag, and therefore when used as the source for data binding, the target will never update.
Second off, even if you created your own non-generic object with properties being bindable, binding doesn't usually go multiple levels deep into an object's properties of an array.
The selectedItem will point to an object like are in your _dataProvider, or possibly null, based on user interaction with the dropDownList. Binding the selectedItem to a property inside the item doesn't make sense; because you'd be comparing an literal to an object and nothing would ever be selected.
I'm unclear, without looking, what happens in the DropDownList when you try to set selectedItem to an item not in your dataProvider. I imagine it resets the selection, though.
If you can expand on what exactly you're trying to accomplish we may be able to help more.
<s:DropDownList id="ddl" dataProvider="{_dataProvider}"/>
<s:Label text="{ddl.selectedItem.data.toString()}"/>
Yes. You can do this. It is quite simple, actually. I do it all the time:
<s:DropDownList dataProvider="{_dataProvider}" selectedItem="#{_selectedItem}" />
With ActionScript that looks like this:
private var _dataProvider:ArrayCollection = new ArrayCollection([{label:"one", data:1}, {label:"two", data:2}]);
[Bindable] private var _selectedItem;
Every time the user selects an item in the drop down list, the _selectedItem will get set.