How do I create a bidirectional data binding in Flex 3? - apache-flex

I need to bind a property to an edit control and have the control write its value back to the same property. The problem is, I'm setting the source value before the control is created:
<mx:Panel>
<mx:Script>
<![CDATA[
[Bindable] public var editedDocument: XML;
]]>
</mx:Script>
<mx:TextInput id="docLabel" text="{editedDocument.#label}"/>
<mx:Binding source="docLabel.text" destination="editedDocument.#label"/>
</mx:Panel>
I call this like so:
var xmlDoc: XML = <document label="some label" />;
var myPanel: MyPanel = new MyPanel();
myPanel.editedDocument = xmlDoc;
parent.addChild(myPanel);
What happens is this:
the docLabel text field ends up blank (equal to "")
the xmlDoc's #label attribute is set to ""
What I want is this:
the docLabel text field should contain "some label"
the xmlDoc's #label attribute should change only when the docLabel's text property changes.
How do I accomplish this, using Flex 3?
Edit
I have also tried this:
<mx:Panel>
<mx:Script>
<![CDATA[
[Bindable] public var editedDocument: XML;
]]>
</mx:Script>
<mx:TextInput id="docLabel"/>
<mx:Binding source="editedDocument.#label" destination="docLabel.text"/>
<mx:Binding source="docLabel.text" destination="editedDocument.#label"/>
</mx:Panel>
The result is the same.

You can try using BindingUtils to programmatically create the binding after the class has been created:
http://life.neophi.com/danielr/2007/03/programmatic_bindings.html
There are many variations of this that I've used to tackle similar problems. If you can't figure it out from the link post a comment and I'll dig through my source code and see what I can find.
private function init():void
{
var xmlDoc: XML = <document label="some label" />;
var myPanel: MyPanel = new MyPanel();
myPanel.editedDocument = xmlDoc;
parent.addChild(myPanel);
BindingUtils.bindProperty(docLabel, "text", editedDocument, "label");
//or maybe it should be one of these, I have not done binding to an XML attribute before
BindingUtils.bindProperty(docLabel, "text", editedDocument, "#label");
BindingUtils.bindProperty(docLabel, "text", editedDocument, "{#label}");
}

Take a look at Two-way data binding.
Take a look at the part of the text:
In Flex 3, if you want to set
two-way binding using the
mx:Binding
tag you need to set it twice:
mx:Binding source="a.property" destination="b.property"/>
mx:Binding source="b.property" destination="a.property"/>
which becomes:
mx:Binding source="a.property" destination="b.property" twoWay="true"/>

In Flex 3 you would be better of doing something like this. Also not sure you can bind directly to XML?
Instead do something like this:
[Bindable] public var tmpString: String;
public var onChange():void {
tmpString = docLabel.text;
//set the XML string, etc.
}
]]>
</mx:Script>
<mx:TextInput id="docLabel" text="{tmpString}" change="onChange()" />

I think bidirectional data binding is a new feature in Flex 4.

This is straight from Adboe http://opensource.adobe.com/wiki/display/flexsdk/Two-way+Data+Binding, and it's Flex 3 too!

I created custom controls that programmatically create two-way bindings when given a provider object that has a suitable property whose name matches the control's id. Here's an example for a TextInput:
public class BoundTextInput extends TextInput
{
// some code omitted for brevity:
// Create getter / setter pair, call invalidateProperties()
// and set internal flag for efficiency
// create bindings in commitProperties:
override protected function commitProperties():void
{
if (this._fProviderChanged) {
this._fProviderChanged = false;
if (null != this._provider && this._provider.hasOwnProperty(this.id) && this._provider[this.id] is String) {
// this is the core bit
BindingUtils.bindProperty(this, "text", this._provider, this.id);
BindingUtils.bindProperty(this._provider, this.id, this, "text");
}
}
// Normally, you call the overridden method first,
// but we want to see the values initialized by the new
// binding right away, so we first create the bindings
// and then commit all inherited properties
super.commitProperties();
}
}
This is an example of how I use it inside one of my other components (a popup dialog). The data property is set to an instance of the appropriate model class, which is always a dumb, [Bindable] container.
<?xml version="1.0" encoding="utf-8"?>
<PopUp xmlns="com.econemon.suite.gui.components.*" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Form width="100%" height="100%" >
<mx:FormHeading label="Server-URL" />
<mx:FormItem label="URL" >
<!--
If it is available, and of type String, data.urlServer
is bound to the text property of this TextInput
-->
<BoundTextInput id="urlServer" provider="{this.data}"/>
</mx:FormItem>
<mx:FormItem>
<mx:Button label="OK" click="this.submit(event)" />
</mx:FormItem>
</mx:Form>
</PopUp>

Related

Pass value from Textinput to Controller file

I am currently trying to put together a simple Illustrator plugin, and coming from a design background this is proving to be quite a task, I have experience with JS, but not with Flex.
What I want to do is to have a panel in Illustrator, with an input field and a button. You type something in the input and press the button and a text frame with the desired text is added to the canvas.
But how do I pass the value from a mx:Textinput to the Controller.as file? I couldn't find an answer on the web.
This is my main.mxml file:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" historyManagementEnabled="false">
<mx:Script>
<![CDATA[
private var c:Controller = new Controller();
]]>
</mx:Script>
<mx:VBox height="100%" width="100%" verticalAlign="middle" horizontalAlign="center">
<mx:Label text="myVariable"></mx:Label>
<mx:TextInput name="TextValue"/> // I want the text value to be passed to the Controller class so I can pass it on to my JSX function
<mx:Button label="Run" click="c.run()"/>
</mx:VBox>
</mx:Application>
And this is my Controller.as file:
package
{
import flash.external.HostObject;
public class Controller
{
[ Embed (source="myScript.jsx" , mimeType="application/octet-stream" )]
private static var myScriptClass:Class;
public function run():void {
var jsxInterface:HostObject = HostObject.getRoot(HostObject.extensions[0]);
jsxInterface.eval( new myScriptClass ().toString());
//calling from AS to JSX
jsxInterface.myJSXFunction (myVariable); //This is where I want the value to be passed to
}
}
}
You might also pass the string directly to the c.run() call.
public function run(myString:String):void {
...
jsxInterface.myJSXFunction (myString)
...
and then
<mx:TextInput id="TextValue"/>
<mx:Button label="Run" click="c.run(TextValue.text)"/>
Just another approach.
Loic
First declare public property public var myTextValue : String; in your Controller.
Then declare bidirectional binding in your MXML <mx:TextInput text="#{c.myTextValue}"/>
Now you have myTextValue property always containing the actual value.
But bidirectional binding was introduced not that long time ago.
Alternatively, you can add a change event listener to your TextInput instance <mx:TextInput id="myTextInput" change="c.myTextValue = myTextInput.text"/>

ItemRender data change

I have a List with an ItemRenderer. When I click a button, then a property of the ArrayCollection I bound to the list is changed.
When I click the button, then it does change the property, but the list doesn't change.
How do I solve this.
Here's my code
<fx:Script>
<![CDATA[
[Bindable]
public var controllers:ControllerCollection = new ControllerCollection();
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
//these methods don't work unfortunatly
controllers.itemUpdated(controllers[0]);
controllers.refresh();
}
]]>
</fx:Script>
<mx:List id="listControllers" dataProvider="{controllers}">
<mx:itemRenderer>
<fx:Component>
<solutionItems:displaySolutionItem visible="{data.meetsRequirements}" />
</fx:Component>
</mx:itemRenderer>
</mx:List>
<mx:Button label="test" click="hideTheFirstItem(event)" />
(ControllerCollection extends ArrayCollection)
Thanks!
Vincent
Two ways:
collection.refresh()
collection.itemUpdated()
Of course, ControllerCollection is not a standard Flex Collection class; so I am just assuming that it implements the ICollectionView interface.
Update:
I do notice that your code is set to modify the first element of the ArrayCollection
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
//these methods don't work unfortunatly
controllers.itemUpdated(controllers[0]);
controllers.refresh();
}
I wanted to be sure to specify that the first element of the collection may not be the first element currently visible in the view. I wonder if that is causing you issues.
Without seeing your item renderer, I need to make some assumptions.
First, I will assume that your item renderer is using data binding to the meetsRequirements property. If that is the case, then the meetsRequirements property needs to notify when that property changes. If you add the [Bindable] tag to that property or the Controller class, then the meetsRequirements property will notify the itemRenderer to update that field based on your data binding.
If my assumptions are wrong, we need to see the code to give you any further thoughts.
First, don't try to create new collections if you don't need to.
I believe your problem lies with this statement: (controllers[0] as Controller).meetsRequirements = false; which should fail on compile because a collection item cannot be retrieved using the square bracket annotation. You need to use getItemAt(index:int) function.
Furthermore, you wouldn't want to set visible to false to an item renderer if you want to 'remove' it because then you'd have an empty spot. What you want to do is filter it out:
<s:Application creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] public var data:ArrayCollection = new ArrayCollection();
private function onCreationComplete():void
{
// TODO: need to add data here
// Adding filter function
data.filterFunction = filterItems;
}
private function filterItems(item:Object):Boolean
{
return item.meetsRequirements;
}
private function hideFirstItem():void
{
if(data.length > 0)
{
Controller(data.getItemAt(0)).meetsRequirements = false;
}
data.refresh();
}
]]>
</fx:Script>
<mx:List id="listControllers" dataProvider="{data}" />
<mx:Button label="test" click="hideFirstItem()" />
</s:Application>
This should do it. Untested though.
Try this:
<fx:Script>
<![CDATA[
[Bindable(Event="refreshMyList")]
public var controllers:ControllerCollection = new ControllerCollection();
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
dispatchEvent(new Event("refreshMyList"));
}
]]>
</fx:Script>

Flex 4 . Passing data from Main App to custom component

I havent found quite what I want elsewhere or at least am not accomplished enough to adapt
I'm trying to create a project that shows radio stations with logo, listen button etc
The datasource is an xml file with child tags like 'name','image' and 'listen' for each parent, 'station'.
I access this data via the main application with creationComplete="radioService.send()"
I can then use it on the main app via dataProvider="{radioService.lastResult.stations.station}
However, I want to use the results to populate variables in the custom component in both the components themselves
hard coded examples
mx:Image source="Images/BBC5.gif" id="bbc5Logo" "/>
mx:LinkButton id="bbc5Listen" click="bbc5Listen_clickHandler(event)" />
and in the clickhandler
protected function bbc5Listen_clickHandler(event:MouseEvent):void
{
var url:String = "http://www.bbc.co.uk/iplayer/console/bbc_radio_five_live";
var request:URLRequest = new URLRequest(url);
}
Any help, much appreciated
I done something similar before though I am not sure of what look and feel you wish your app to have but it would be possible to create a Datagrid and bind the datasource to that grid.
Then you can add fields to the datasource like ( img field and use the link in the XML by setting the xpath towards that field) , it would also be possible to create a button and place that in the row of the datagrid for listen.
Obviously this is a slightly different implementation but the same principle applys if you have created a custom component then make sure it makes use of DataSource and DataSource fields which will aquire the data from the right part of the XML. Then it would be possible to map that DataSource field to a String for example.
Hope this helps
You can actually use Custom Events, where you can pass values from your main app to your custom component.
http://www.anujgakhar.com/2009/03/04/flex-passing-data-with-custom-events/
Another approach could be to create Value Objects from your XML and bind your components to these objects, this way you would decouple your components from the XML , which can be handy if at some point in the future, you don't want to use XML, but JSON for instance.
Each Value Object would have the properties defined in your question , i.e , imageSource , id , url etc... and each component would be bound to one Value Object
Edit
Have a look at this video training, it covers data retrieval from an XML , ArrayCollections of ValueObjets etc...
http://www.adobe.com/devnet/flex/videotraining.html
there are two ways to accomplish this:
you should wrap your data structure
from xml source to a [Bindable] actionscript
object (not mandatory, but you can
handle it easier), and bind the array of these data instances (let's say RadioChannel.class) as the dataProvider of your grid. Inside your itemRenderer implementation you can access the current row's RadioChannel instance via the data getter/setter:
RadioChannel.as:
[Bindable]
public class RadioChannel {
private var _name: String;
private var _imageUrl: String;
private var _listenUrl: String;
public function RadioChannel()
{
}
public function get name():String {return _name;}
public function set name(value:String):void {_name = value;}
public function get imageUrl():String {return _imageUrl;}
public function set imageUrl(value:String):void {_imageUrl = value;}
public function get listenUrl():String {return _listenUrl;}
public function set listenUrl(value:String):void {_listenUrl = value;}
public function toString():String
{
return 'RadioChannel ' + name + ' - image: ' + imageUrl +
' | listen at ' + listenUrl;
}
}
RadioList.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:DataGrid xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx" dataProvider="{radioChannels}">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var _radioChannels:ArrayCollection = new ArrayCollection();
[Bindable]
private function get radioChannels():ArrayCollection {return _radioChannels;}
private function set radioChannels(value:ArrayCollection):void {_radioChannels = value;}
]]>
</fx:Script>
<mx:columns>
<mx:DataGridColumn>
<mx:itemRenderer>
<fx:Component>
<mx:Image source="{data.imageUrl}">
<fx:Script>
<![CDATA[
public override function set data(value:Object):void
{
super.data = value;
trace('current instance: ' + RadioChannel(data).toString());
}
]]>
</fx:Script>
</mx:Image>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn dataField="name" />
<mx:DataGridColumn>
<mx:itemRenderer>
<fx:Component>
<mx:HBox>
<fx:Script>
<![CDATA[
protected function onLinkClicked(event:MouseEvent):void
{
var currentChannel:RadioChannel = RadioChannel(data);
if (data)
{
var request:URLRequest = new URLRequest(currentChannel.listenUrl);
[...]
}
}
]]>
</fx:Script>
<mx:Button click="onLinkClicked(event)" label="Listen" />
</mx:HBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
The other way is to build up custom
events that deal with an extra
parameter: the url of the channel.
These custom events should cascade back to the list component, where they should be handled.
Since for the second solution you would still need the first's binding as well, i'd choose the first one, and handle the click event locally.
hope this helped.
I think this may be useful, to pass the value from mainApp to custom component you can follow this method.Since you have the value in mainApp,in the custom component you can access the value by using parentDocument object.parentDocument.rootAppVar

Using a composite MXML component from ActionScript

I'm trying componentize one of the pieces of UI in an AIR application that I'm developing in Flex. In this example, I want to display file information on a single line (which has an icon, some text/link and the size).
My code looks like this (component is called FileDisplay):
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
public function set iconType(source:String):void {
this.ficon.source = source;
}
public function set fileName(name:String):void {
this.fname.htmlText = name;
}
public function set fileSize(size:String):void {
this.fsize.text = size;
}
]]>
</mx:Script>
<mx:Image id="ficon" />
<mx:Label id="fname" left="20" right="30" text="Filename" />
<mx:Label id="fsize" right="0" text="0 K" />
</mx:Canvas>
When I'm using this component in my main application, the actionscript looks like:
for each (var file:XML in result.files) {
var fd:FileDisplay = new FileDisplay();
fd.fileName = ''+file.name+'';
fd.iconType = getFileTypeIcon(file.name);
fd.fileSize = getFileSizeString(file.size);
this.file_list.addChild(fd);
}
However, when I do this, I get an error: Error #1009: Cannot access a property or method of a null object reference. This is because the child components of the FileDisplay are null (or at least they show up that way in the debugger).
Does anyone know if there's a way around this? Am I supposed to be waiting for events indicating the child components were created? Is there a more common pattern that solves this problem?
For now I can manually do everything in ActionScript in my main app (create a Canvas and add children to it) but I would appreciate any insight on how to separate the code more cleanly.
Bindable to the rescue:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
[Bindable]
public var iconType:String;
[Bindable]
public var fileName:String = "Filename";
[Bindable]
public var fileSize:String = "0 K";
]]>
</mx:Script>
<mx:Image id="ficon" source="{iconType}"/>
<mx:Label id="fname" left="20" right="30" text="{fileName}" />
<mx:Label id="fsize" right="0" text="{fileSize}" />
</mx:Canvas>
the values will be automatically updated when the components are created.
The subcomponents haven't been loaded yet.
Read this: http://livedocs.adobe.com/flex/3/html/help.html?content=ascomponents_advanced_2.html#203434.
Then, when like me, you don't understand it (and it's not reliable), listen for the FlexEvent.CREATION_COMPLETE within FileDisplay, and apply your child component properties there.
Or better yet, create the three children programmatically in the "createChildren" function, and apply the settings there.
Both of these methods assume that you're setting filename, icontype, and filesize as local members before applying them to the children components, which you should be doing regardless.
What is the parent component that holds the FileDisplay component? If you're sure that the error is coming from the fact that the child components of FileDisplay aren't being instantiated then you might want to look at the creationPolicy attribute and make sure it's set to ContainerCreationPolicy.ALL on that parent component.
=Ryan
In addition to setting the CreationPolicy to all, you need to add the DisplayObject to the stage via addChild. The children of FileDisplay are not created until you add it is added to the stage. So do:
for each (var file:XML in result.files) {
var fd:FileDisplay = new FileDisplay();
this.file_list.addChild(fd);
fd.fileName = ''+file.name+'';
fd.iconType = getFileTypeIcon(file.name);
fd.fileSize = getFileSizeString(file.size);
}

How can I target a Flex 3 datagrid in MXML from Actionscript?

I have a datagrid defined in an mxml file (flex 3):
I am using an external class to connect to a sqlite database and generate some results (this is working and I can trace the results).
How can I target the datagrid generated in the mxml from the external class? I have tried:
Application.application.resultsGrid.dataProvider = results.data;
And get 'Error: Access of undefined property Application.' from the amxmlc compiler.
I've also tried:
[Bindable]
public var resultsGrid:DataGrid;
In the class properties.
Looks like I needed to include import mx.core.*; and it now works.
I don't really understand your answer. Am I not binding the dataprovider property by doing:
Application.application.resultsGrid.dataProvider = result.data; ?
I'm from a PHP background and familiar with OOP in that environment so the idioms in Flex are quite strange to me.
as brd664 says, what you are actually doing in
Application.application.resultsGrid.dataProvider = result.data;
is actually an assignment. It's just like assigning a value to variable as in
var a : uint = 1;
Binding gives you a little more structure and allows you to populate multiple components based on a single property update. There's a ton of other benefits from binding and probably too much to cover in this post.
Here is a quick and simple example of how binding works. Note that there is one property that is bindable... when you click the button it sets that property to the value of whatever is in the textInput. This update then causes the bindings to fire and updates anything that has been bound to that property. It's one of flex's biggest features (it's also used extensively in silverlight and wpf and probably a load of other technologies that i'm not aware of). Anyway... have a play with it and see if you can get your component to update from a binding.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal">
<mx:Script>
<![CDATA[
private var _myData : String
[Bindable]
public function get myData() : String
{
return _myData;
}
public function set myData(value : String) : void
{
_myData = value;
}
private function clickHandler(event : MouseEvent) : void
{
myData = myTextInput.text;
}
]]>
</mx:Script>
<mx:VBox>
<mx:HBox>
<mx:Label text="{myData}" />
<mx:Label text="{myData}" />
<mx:Label text="{myData}" />
</mx:HBox>
<mx:TextInput id="myTextInput" text="TYPE HERE" />
<mx:Button label="CLICK TO BIND" click="clickHandler(event)" />
</mx:VBox>
</mx:Application>
Update: The phrasing of your question confused me :(
If you need to populate the datagrid with from you db, you really should be looking at binding the dataProvider property.

Resources