How to add a 'Graphic' to an itemrenderer at runtime? - apache-flex

May be this is very simple but right now I am unable to put a solution for it. Here is brief description of my problem:
I have a dictionary of clipart objects as:
clipartDict['cat'] = Cat; //Cat.mxml
clipartDict['dog'] = Dog; //Dog.mxml
Cat.mxml:
<s:Graphic>
<s:Path x="2.86723" y="-0.000106812" data="M3.45943 80.3419C3.06051 77.3605 0.002399>
</s:Path>
</s:Graphic>
MyView.mxml (relevant code):
<s:SkinnableDataContainer width="300" dataProvider="{clipArts}">
<s:layout>
<s:TileLayout requestedColumnCount="1"/>
</s:layout>
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer>
<fx:Script>
<![CDATA[
import models.vo.ClipArtVO;
// (data as ClipArtVO).clipArtFileName represents the 'key' in dictionary.
// Now, how can I display the relevent clipart from dict based on the key
// this.addElement throws an Type Coercion error
]]>
</fx:Script>
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:SkinnableDataContainer>
Can anyone suggest me a solution or any idea to implement it in any different way?
Thanks.

What you've put in that Dictionary are Class references and not instances. You'll have to create an instance of the desired Graphic to add it to the displayList. So there are two methods to fix your issue.
Method 1
Put instances of the Graphics in the Dictionary (instead of Class references):
clipartDict['cat'] = new Cat();
clipartDict['dog'] = new Dog();
Then simply add it to the displayList:
var graphic:Graphic = clipartDict[(data as ClipArtVO).clipArtFileName];
addElement(graphic);
Method 2
Create an instance of the Class reference on the fly. We keep the Dictionary as it was:
clipartDict['cat'] = Cat;
clipartDict['dog'] = Dog;
and create an instance and add it to the displayList like this:
var Graphic:Class = clipartDict[(data as ClipArtVO).clipArtFileName];
addElement(new Graphic());

You should be able to wrap your cat.mxml file inside a SpriteVisualElement. It's a bit of tedium, but basically do this:
protected var sprite :SpriteVisualElement;
protected var class : Class;
protected var graphicInstance : Graphic;
protected function dataChangeMethod():void{
// (data as ClipArtVO).clipArtFileName represents the 'key' in dictionary.
// You told us about the key, but not the value. I'm assuming the value is an actual class
// and not an instance of the class
class = (data as ClipArtVO).clipArtFileName;
// create an instance of the class
graphicInstance = new class();
// create the new spriteVisualElement instance
sprite = new SpriteVisualElement();
// Add the graphic instance as a child to the spriteVisualElement
// you may need to do any sizing of the graphicInstance before adding it
sprite.addChild(graphicInstance);
// add the Sprite Visual Element as a child to your container
this.addElement(sprite);
}

Related

Flex embedding an image where the path is based on a bound variable

I am trying to embed an image using an item renderer in my Flex project.
The image path however is a String passed in as a bound variable.
I am aware that
<s:BitmapImage source="#Embed('/../assets/image.png')" />
works because the image is embedded at runtime? (Could someone please clarify this)
How would i go about embedding my bound string, somewhat like this:
<s:BitmapImage source="#Embed('/../assets/{data.image}')" />
Many Thanks
I think a better choice if you'd like to embed the image but find it dynamically at runtime is: Embed all of the images it could be and then grab a reference to it dynamically. We generally use a pattern like this:
public class Icons {
[Embed(source="icons/icon1.png")]
public var icon1:Class;
[Embed(source="icons/icon2.png")]
public var icon2:Class;
}
Then you can dynamically grab the embedded images from your Icons instance at run-time.
Edit - self contained example - I'll use an item renderer since I think that's what you're doing.
Let's assume data.image can be 'plane' 'train' or 'automobile'
<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:Script>
<![CDATA[
[Embed(source="/assets/icons/plane.png")]
public var plane : Class;
[Embed(source="/assets/icons/train.png")]
public var train : Class;
[Embed(source="/assets/icons/automobile.png")]
public var automobile : Class;
]]>
</fx:Script>
<s:Image source="{this[data.image]}"/>
</s:ItemRenderer>
This is a really simple example and not the BEST way to implement, but you get the idea.
I like embed icons with css files. Then in ItemRenderer you can set css class and get image which you want.
css file or mxml css block:
.icons
{
bender: Embed(source="/assets/bender.png");
/* other icons */
}
In renderer, when you override set data method:
override public function set data(value:Object):void
{
super.data = value;
var iconName:String = data.image;
if ( iconName )
{
var cssDecl2:CSSStyleDeclaration = styleManager.getStyleDeclaration(".icons");
var IconClass:Class = cssDecl2.getStyle( iconName );
bmImage.source = new IconClass();
}
}
and bmImage as id s:BitmapImage:
<s:BitmapImage id="bmImage" />

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 AS3: Dynamically Create Graphic with BitmapImage Child

I've trawled the net trying to find a solution, but everything seems to be mxml-centric. What I want is to dynamically create a series of Graphics objects each with a child BitmapImage. However, this doesn't seem to work:
var bmi:BitmapImage = new BitmapImage();
bmi.source="#Embed('custom-case.png')";
var gr:Graphic = new Graphic( );
gr.addElement( bmi );
gr.x = 50;
gr.y = 50;
this.addElement( gr );
Whereas, this does:
<s:Graphic x="250" y="250">
<s:BitmapImage source="#Embed('custom-case.png')">
</s:BitmapImage>
</s:Graphic>
Thanks in advance for any ideas.
Paul
it is quite different in AS3, you have to define a variable class type like shown below.
[Embed("custom-case.png")]
private var someImage:Class;
...
bmi.source=someImage;
To follow up Shruti's comment/question (I am unable to post comment since my current reputation insufficent):
The requirement for dynamic updating of images with mxml is the same as indicated with the original answer, which is that any images you might want to dynamically change to must be pre-embeded in your mxml:
[Embed(source="image.png")] private var theImage:Class;
which can be later used to update an image source as such:
<fx:Script>
<![CDATA[
[Embed(source="image.png")] private var theImage:Class;
private function updateImage():void {
image.source = theImage;
}
]]>
</fx:Script>
<s:BitmapImage id="image" source="#Embed('defaultImage.png')"/>

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

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

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>

Resources