Not Understanding Basics Of Dynamic DataBinding (bindPropety) In Flex - apache-flex

I need to dynamically bind properties of components created at runtime. In this particular case please assume I need to use bindProperty.
I don't quite understand why the following simplistic test is failing (see code). When I click the button, the label text does not change.
I realize that there are simpler ways to go about this particular example using traditional non-dynamic binding, but I need to understand it in terms of using bindProperty.
Can someone please help me understand what I'm missing?
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:ns1="Tools.*" minWidth="684" minHeight="484" xmlns:ns2="*" creationComplete="Init();">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.binding.utils.*;
public var Available:ArrayCollection=new ArrayCollection();
public function get Value():String {
return (Available.getItemAt(0).toString());
}
public function Init():void {
Available.addItemAt('Before', 0);
BindingUtils.bindProperty(Lab, 'text', this, 'Value');
}
public function Test():void {
Available.setItemAt('After', 0);
}
]]>
</mx:Script>
<mx:Label x="142" y="51" id="Lab"/>
<mx:Button x="142" y="157" label="Button" click="Test();"/>
</mx:WindowedApplication>
Thanks in advance.

As mentioned by Glenn, you need to add [Bindable] tag on Value.
Also, you haven't defined a setter for the property. Data binding is invoked only when the corresponding setter is called. The flow is something like: you call the setter - Flex updates the data by calling the getter.
[Bindable]
public function get value():String {
return (Available.getItemAt(0).toString());
}
public function set value(v:String):void {
Available.setItemAt(v, 0);
}
public function init():void {
Available.addItemAt('Before', 0);
BindingUtils.bindProperty(Lab, 'text', this, 'Value');
}
public function iest():void {
value = "After";
}
Note that I've changed names of functions and properties to lowercase as per the normal convention. InitialCaps are used only for class names.

I never use the BindingUtils, but my first guess is that you're missing the [Bindable] tag on "Value".

Related

How to pass parameters to a view that was previously shown in Flex Mobile Project (I cannot use pushView)

I have a Flex Mobile Project that I am working on. I am trying to pass parameters from one view to another. The only problem is that I cannot use navigator.pushView to push the View and the parameter as the view I am pushing to was the previous view. So this wipes out using the addHandler() and the returnObjectsCreated() as I cannot use pushView. I am having to use popView because it is my previous view that I have to pass parameters too. Any help would be appreciated.
That class that has the parameters I need to pass is below. It is a view that shows a list. So I need to pass list.selectedItem to the popview or previous view...
<?xml version="1.0" encoding="utf-8"?>
<amec:BaseBrowseView xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:amec="com.amec.Components.*"
title="Select an item">
<fx:Script>
<![CDATA[
import com.amec.BaseSql;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
import spark.events.IndexChangeEvent;
[Bindable]private var resultArr:ArrayCollection = new ArrayCollection();
protected function myList_changeHandler(event:IndexChangeEvent):void
{
navigator.popView();
//Either send a ref to the last view or override createReturn
}
[Bindable(event="myDataChanged")]
private function get myData():ArrayCollection
{
}
]]>
</fx:Script>
<s:List id="list"
height="100%" width="100%"
dataProvider="{myData}"
labelField="DMV_VALUE_1"
change="myList_changeHandler(event);">
</s:List>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
Now below is the previous view that I want to popView to that I need to pass parameters to so I can populate the TextInput with.
<?xml version="1.0" encoding="utf-8"?>
<amec:BaseControl xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:amec="com.amec.Components.*"
horizontalCenter="true">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
[Bindable]
protected var textValue:String;
protected function control_creationCompleteHandler(event:FlexEvent):void
{
// todo: get control data from view.data
}
protected function control_clickHandler(event:MouseEvent):void
{
parentView.navigator.pushView(TextListView);
}
]]>
</fx:Script>
<s:Label text="Robert says something meaningful goes here" />
<s:TextInput id="ns" text="{textValue}" editable="false" click="control_clickHandler(event)"/>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
Again I cannot use pushView as the view is already on the stack.
In case you want to do it the right way, on the previous view (the one you want the result going to, add an eventListener for 'add'.
protected function addHandler(event:FlexEvent) : void {
if (navigator.poppedViewReturnedObject != null) {
var returnedObject : Array = navigator.poppedViewReturnedObject.object as Array;
if(!returnedObject) return;
yourData= new ArrayCollection(returnedObject);
}
}
On the view that you are going to pass the object from:
override public function createReturnObject():Object {
var returnedObject : Object = new Object();
returnedObject = dataYouWantToSendBack;
return returnedObject;
}
I looked at using static variables but that was a little messy, I also looked at using Dependency Injection but spending a few hours to set up the framework and get it running is to costly when all I am doing is passing parameters on a popView, so what I came up with is the similar approach I have used when doing Native Android Apps with the Android SDK. A Singleton with getters and setters.
Basically the way I have been able to do this is with the use of a Singleton sharedInstance. See code below:
package com.controls.Text
{
import flash.utils.Dictionary;
[Bindable]
public class ParameterManager
{
private static var instance:ParameterManager = new ParameterManager();
private var dictionary:Dictionary=new Dictionary();
public function ParameterManager( )
{
if (instance != null) { throw new Error('Must use ParameterManager.getInstance().') }
}
public static function getInstance(): ParameterManager {
return instance;
}
public function setParameter(key:*, value:*):void
{
dictionary[key]=value;
}
public function getParameter(key:*):*
{
return dictionary[key];
}
}
}
The method setting the value of the parameter:
protected function myList_changeHandler(event:IndexChangeEvent):void
{
var listViewReturnObject:String = new String();
listViewReturnObject = list.selectedItem.DMV_VALUE_1;
ParameterManager.getInstance().setParameter("selectedItem", listViewReturnObject);
navigator.popView();
}
The method getting the value:
protected function control_creationCompleteHandler(event:FlexEvent):void
{
var listViewReturnObject:Object = new Object();
listViewReturnObject= ParameterManager.getInstance().getParameter("selectedItem");
if (listViewReturnObject != null)
{
dataTxt.text= String(listViewReturnObject);
ParameterManager.getInstance().setParameter("", null); //Make sure objects are cleared when done.
}
}

How can you programmatically make the "mouseover" item into the selected item in a tree/list?

I would like to programmatically change a selected item, in a tree or list, to the item currently "marked/focused" under the mouse pointer .
I'm working with an Flex Air standalone application.
I was thinking in the lines of:
myTree.selectedItem = EVENT.TARGET (where EVENT could be a mouseover/rightclick/rollOver event, and TARGET should be the node/item currently under the mouse pointer).
Is there a way of doing this (or in any other way)?
Ahh, and i want to do it without left clicking ;-)
Thank you in advance,
Sebastian
I found this interesting enough so I am asking if this is the easiest way to achieve this. First off, instead of the list, you need to add the rollOver-listener to the ItemRenderer, not to the list itself (as the event.target and event.currentTarget will just show your list).
So lets create a custom ItemRenderer and add a rollOver listener
<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" height="20" rollOver="itemrenderer1_rollOverHandler(event)">
<fx:Script>
<![CDATA[
protected function itemrenderer1_rollOverHandler(event:MouseEvent):void
{
this.dispatchEvent(new CustomEvent(CustomEvent.SELECT_ITEM, data, true));
}
]]>
<s:Label id="label1" text="{data.label}"/>
</s:ItemRenderer>
You need to somehow get the value of the selected item (which is the data on the itemRenderer) so I created a CustomEvent-class just to do so.
package
{
import flash.events.Event;
public class CustomEvent extends Event
{
public var selectedItem:Object;
public static const SELECT_ITEM:String = "selectItem";
public function CustomEvent(type:String, selectedItem:Object, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this.selectedItem = selectedItem;
}
}
}
then I added a eventListener to the main class and set the list.selectedItem property accordingly:
//for the main MXML initializer:
this.addEventListener(CustomEvent.SELECT_ITEM, rollOverChangeSelected);
//and the function:
protected function rollOverChangeSelected(ce:CustomEvent):void{
list.selectedItem = ce.selectedItem;
}
Another way: bindable variable
The list:
s:List id="list" allowMultipleSelection="true" selectionColor="red" rollOverColor="red" itemRenderer="customItemRenderer" selectedItem="{_rollOverSelectedItem}">
The variable and set / get methods:
[Bindable] public var _rollOverSelectedItem:Object;
public function get rollOverSelectedItem():Object
{
return _rollOverSelectedItem;
}
public function set rollOverSelectedItem(value:Object):void
{
_rollOverSelectedItem = value;
}
and the ItemRenderer's rollOver-method:
protected function itemrenderer1_rollOverHandler(event:MouseEvent):void
{
this.parentApplication.rollOverSelectedItem = data;
}
What is the best/proper way?

How to set text property on keyDownHandler on TextInput in Flex 3

Simple and straight forward.
I extended a mx.controls.TextInput to create a custom component with a different behavior.
I'm trying to set the text property on the keyDownHandler(), and for some reason it doesn't work as I expected. The text on the component just kinda ignore the change.
I'm using Flex 3.6.
Down here is a simple example code that explains what's going on:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:customcomponent="com.test.customcomponent.*">
<customcomponent:TextTest x="20" y="20"/>
</mx:Application>
And below de AS class:
package com.test.customcomponent
{
import flash.events.KeyboardEvent;
import mx.controls.TextInput;
public class TextTest extends TextInput
{
public function TextTest()
{
super();
}
override protected function keyDownHandler(event:KeyboardEvent):void{
text = "lol. It doesn't work";
}
}
}
You need to prevent the default key down event.
override protected function keyDownHandler(event:KeyboardEvent):void{
text = "lol. It doesn't work";
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation()
}
In order to be able to prevent the default handling, you have to have a high enough priority on your handler (which keyDownHandler() does not have). This means you need to register your own method with priority > 0.
You can try like this:
public function MyTextInput() {
addEventListener(KeyboardEvent.KEY_DOWN, yourHandler,
false, EventPriority.BINDING, true);
...
}
private function yourHandler(event : KeyboardEvent) : void {
// stop further handling
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
// do your work here
text = ...;
}

How do I populate required parameters in a custom MXML tag?

Here's the Class:
package fnc {
import mx.containers.Canvas;
public class Deck extends Canvas {
protected var _chipCount:int;
public function Deck(chipCount:int) {
/* Associate some chips with this deck */
_chipCount = chipCount;
}
public function get chipCount():int {
return _chipCount;
}
}
}
Here's the MXML:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:ns1="fnc.*">
<ns1:Deck horizontalCenter="0" verticalCenter="0">
</ns1:Deck>
</mx:Application>
Running this application gets this error:
ArgumentError: Error #1063: Argument count mismatch on fnc::Deck(). Expected 1, got 0.
at mx.core::Container/createComponentFromDescriptor()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:3579]
at mx.core::Container/createComponentsFromDescriptors()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:3493]
at mx.core::Container/createChildren()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:2589]
at mx.core::UIComponent/initialize()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:5370]
at mx.core::Container/initialize()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:2526]
at mx.core::Application/initialize()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Application.as:846]
at Practice/initialize()[C:\Documents and Settings\LocalService\My Documents\Flex Builder 3\Practice\src\Practice.mxml:0]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::childAdded()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:2009]
at mx.managers::SystemManager/initializeTopLevelWindow()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3234]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::docFrameHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3064]
at mx.managers::SystemManager/docFrameListener()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:2916]
Adding chipCount="0" to the MXML like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:ns1="fnc.*">
<ns1:Deck chipCount="0" horizontalCenter="0" verticalCenter="0">
</ns1:Deck>
</mx:Application>
Gets this compile error:
Severity and Description Path Resource Location Creation Time Id
Property 'chipCount' is read-only. Practice/src Practice.mxml line 3 1242656555328 26
How do I specify the initial chip count?
You're not able to pass parameters to the constructor of an element when you declare it in MXML. You'll need an empty contructor and then have a property called ChipCount. Your code will also have to be updated to handle ChipCount not being set (or set to 0).
My recommendation would be to change Deck to something like this:
public class Deck extends Canvas {
protected var _chipCount:int;
public function Deck() {
_chipCount = 0; // Default ChipCount and wait for it to be set.
}
public function get chipCount():int {
return _chipCount;
}
public function set chipCount(value:int):int {
// Add logic here to validate ChipCount before setting.
_chipCount = value;
}
}
In answer to brd6644 comment :
package
{
import mx.containers.Canvas;
public class Deck extends Canvas
{
protected var _chipCount:int;
private var chipCountChanged:Boolean;
public function Deck()
{
super();
}
public function set chipCount(value:int):void
{
if (chipCount != value)
{
_chipCount = value;
chipCountChanged = true;
invalidateProperties();
//call invalidateSize() if changing chipCount value may change the size of your component
//call invalidateDisplayList() if changing chipCount value need a redraw of your component
}
}
public function get chipCount():int
{
return _chipCount;
}
override protected function commitProperties():void
{
super.commitProperties();
if (chipCountChanged)
{
chipCountChanged = false;
//here update properties that change because of chipCount new value.
}
}
}
}
I believe that if you're extending a UIComponent you cannot pass arguments to the constructor - you'd have to find another way of setting chip count. I'd suggest listening for the initialize event, and set it then:
<?xml version="1.0" encoding="utf-8"?>
<mx:Script>
public function setChipCount():void{
myDeck.chipCount = 0;
}
</mx:Script>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:ns1="fnc.*">
<ns1:Deck id="myDeck" initalize="setChipCount()" horizontalCenter="0" verticalCenter="0">
</ns1:Deck>
</mx:Application>

How do I implement data binding in an ActionScript Class?

I am having a problem with binding values in my ActionScript components. I basically want to set the value of a a variable in my component to a value in the model, and have the component variable automatically update when the model value is updated. I think that I just don't fully understand how data binding works in Flex - this is not a problem when using MXML components, but, when using ActionScript classes, the binding does not work.
This is the code I'm using, where the values are not binding:
package
{
public class Type1Lists extends TwoLists
{
public function Type1Lists()
{
super();
super.availableEntities = super.composite.availableType1Entities;
super.selectedEntities = super.composite.selectedType1Entities;
}
}
}
package
{
public class Type2Lists extends TwoLists
{
public function Type2Lists()
{
super();
super.availableEntities = super.composite.availableType2Entities;
super.selectedEntities = super.composite.selectedType2Entities;
}
}
}
/* TwoLists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
public var __model:ModelLocator = ModelLocator.getInstance();
public var composite:Composite =
__model.selectedComposite;
[Bindable]
public var availableEntities:ArrayCollection;
[Bindable]
public var selectedEntities:ArrayCollection;
]]>
</mx:Script>
<mx:List id="availableEntitiesList" dataProvider="{availableEntities}" />
<mx:List id="selectedEntitiesList" dataProvider="{selectedEntities}" />
</mx:HBox>
To use binding by code you should use mx.binding.utils.*
Take a look and the BindingUtils.bindProperty and bindSetter methods.
Also, be careful with manual databinding, it could lead you to memory leaks.
To avoid them, save the ChangeWatcher returned by bindProperty and bindSetter and call watcher's unwatch method when is no more used (i.e, in the dipose or destructor method)
You need to add the [Bindable] tag either to the class itself (making all properties bindable) or the properties you want to be [Bindable]. Marking properties or objects as [Bindable] in your MXML is not sufficient.
To fix this, I simply converted the classes to MXML components, and added a private variable for my ModelLocator.
/* Type1Lists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<TwoLists xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
availableEntities="{__model.selectedComposite.availableType1Entities}"
selectedEntities="{__model.selectedComposite.selectedType1Entities}">
<mx:Script>
<![CDATA[
import model.ModelLocator;
[Bindable]
private var __model:ModelLocator = ModelLocator.getInstance();
</mx:Script>
</TwoLists>
/* Type2Lists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<TwoLists xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
availableEntities="{__model.selectedComposite.availableType2Entities}"
selectedEntities="{__model.selectedComposite.selectedType2Entities}">
<mx:Script>
<![CDATA[
import model.ModelLocator;
[Bindable]
private var __model:ModelLocator = ModelLocator.getInstance();
</mx:Script>
</TwoLists>

Resources