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);
}
Related
I am having an issue with using the SplitViewNavigator component in flex 4.6 using Adobe Flash Builder.
[UPDATED]* * *
I am building a reddit app for the blackberry playbook tablet, and am making use of reddit's API. I have three mxml views: RedditReaderHomeView.mxml, redditFeed.mxml, and subredditList.mxml. In RedditReaderHomeView.mxml I have a splitViewNavigator. In left side of my SplitViewNavigator resides subredditList.mxml, and on the right side resides redditFeed.mxml. On initialization, redditFeed.mxml pulls in XML data to populate its list with reddit entries, and subredditList.mxml pulls in XML data which populates its list with subreddits(categories) to display. When a user clicks on of the subreddit entries on the left, the redditFeed.mxml on the right should update so that the data it pulls are entries from the subreddit category that was selected on the left. In other words, classic master/detail navigation. Category on the left, which opens entries of that category on the right.
Well, I have a function that passes the url of the selected subreddit over to redditFeed.mxml.
subredditList.mxml - here a subreddit is selected and its url is sent over to a function in redditFeed.mxml
public function list_clickHandler(event:IndexChangeEvent):void {
var RSSItem:Object = redditList.dataProvider.getItemAt(event.newIndex);
var thisItem:Item = RSSItem as Item;
rlink = thisItem.link;
var moddedLink:String = rlink.slice(1, int.MAX_VALUE)
var pushSub:redditFeed = new redditFeed();
pushSub.myList_creationCompleteHandler(moddedLink);
}
redditFeed.mxml - the url is used to grab data from that particular subreddit...
public function myList_creationCompleteHandler(url:String):void
{
getRedditFeedResult.token = redditFeedGrabber.getRedditFeed(url);
}
... though this data never seems to be displayed in the spark List component, even though I can see the call being made through the Network Monitor.
I've spent hours and hours trying to solve this. And I belive it has something to do with views being 'active' vs. 'deactivated', as when i tried placing this same call from within redditFeed.mxml, the call went through and the data in the list updated. I thought perhaps this was because the action was being performed within the same view that contains the list where the data is displayed. So it would be 'active' in this instance as the action is being performed in that view.
I appreciate any help anyone can offer. Also, dont hesitate to request clarification/more details from me. I reeeallly want to solve this.
Thanks!
RedditReaderHomeView.mxml - Here's the code you requested. Lemme know if you need more/something else.
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
actionBarVisible="false" tabBarVisible="true" title="Reddit Reader by Domisy" creationPolicy="all"
actionBarVisible.landscape="true">
<fx:Script>
<![CDATA[
import com.adobe.fiber.core.model_public;
import mx.events.FlexEvent;
import views.redditFeed;
public var defUrl:String = new String("");
public function refreshRSS(event:Event):void
{
//***UPDATED CODE****
var refreshFunction:Object = redditFeedNav.getElementAt(0);
refreshFunction.refreshList();
//var refreshFunction:redditFeed = new redditFeed();
//refreshFunction.refreshList();
}
]]>
</fx:Script>
<fx:Declarations>
</fx:Declarations>
<s:states>
<s:State name="portrait"/>
<s:State name="landscape"/>
</s:states>
<s:actionContent>
<s:Button includeIn="landscape" click="navigator.pushView(profileView)"
icon="#Embed('assets/images/profileIcon.png')"/>
</s:actionContent>
<s:SplitViewNavigator width="100%" height="100%" id="splitViewNavigator" autoHideFirstViewNavigator="true">
<s:ViewNavigator id="redditListNav" firstView="views.subredditList" width="300" height="100%"/>
<s:ViewNavigator id="redditFeedNav" firstView="views.redditFeed" width="100%" height="100%">
<s:actionContent.landscape>
<s:Button id="refreshButtonlLandscape" icon="#Embed('assets/refresh160.png')" click="refreshRSS(event)" />
</s:actionContent.landscape>
<s:actionContent.portrait>
<s:Button id="refreshButton" icon="#Embed('assets/refresh160.png')" click="refreshRSS(event)" />
<s:Button id="navigatorButton" label="Search" click="splitViewNavigator.showFirstViewNavigatorInPopUp(navigatorButton)" />
</s:actionContent.portrait>
</s:ViewNavigator>
</s:SplitViewNavigator>
</s:View>
Okay I see what the problem is I think. You're creating a new instance of the redditFeed object instead of using the one instantiated in MXML. In your RedditReaderHomeView.mxml give the declaration of redditFeed an id, then in the function above use it like:
private var redditFeedId:RedditFeed; //Passed in from RedditReaderHome, bound to the id of the actual one in there
public function list_clickHandler(event:IndexChangeEvent):void {
var RSSItem:Object = redditList.dataProvider.getItemAt(event.newIndex);
var thisItem:Item = RSSItem as Item;
rlink = thisItem.link;
var moddedLink:String = rlink.slice(1, int.MAX_VALUE)
redditFeedId.myList_creationCompleteHandler(moddedLink);
}
Paste in RedditReaderHome if you end up needing any help on that.
Thanks for following up [ADDED]
public function refreshRSS(event : Event) : void
{
trace(redditFeedNav.getElementAt(0));
redditFeedNav.getElementAt(0).refreshList();
}
I am working on a custom Flex 4 component which is an aggregation of two existing flex components. I would like to be able to specify my own custom properties for the component as well as access the existing public subcomponent properties via MXML. For instance I might want to adjust the font color or style for the label and text input.
A toy component which aggregates both a label and a text input:
<?xml version="1.0" encoding="utf-8"?>
<s:Group 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[
[Bindable] public var prompt:String = "default prompt";
[Bindable] public var input:String = "default inpput";
]]>
</fx:Script>
<s:VGroup>
<s:Label id="cLabel" text="{prompt}" />
<s:TextInput id="cTextInput" text="{input}" />
</s:VGroup>
</s:Group>
Then in my main application I would like to access the public interfaces of the sub-component via mxml without re-writing a pass-through binding for every one. Something like:
...
<local:myInput prompt="name" input="please enter name">
<local:cLabel color="0xffffff" />
<local:CTextInput fontStyle="bold" />
</local:myInput>
In actionscript one can do this easily for all public properties:
myInput.cLabel.color = "0xffffff";
But I am stumped on the syntax for MXML. This seems like it should be easy, but I have not found the answer yet. Any help greatly appreciated.
You can't daisy chain down an display hierarchy w/ the MXML tag/value. You can do it in ActionScript, as you specified, but even that would probably be considered a bad practice.
I'll point out that color on the Label and fontStyle on the TextInput are not properties. They are styles So, the code you have:
myInput.cLabel.color = "0xffffff";
Would most likely throw an error because color is not a property. You'd have to use code like this:
myInput.cLabel.setStyle('color',"0xffffff");
However, since styles are usually inherited by the children; I suspect at the top level component, you can set the style and it would immediately trickle through to the children automatically. So, you should just be able to do:
myInput.setStyle('color',"0xffffff");
Or in MXML:
<local:myInput prompt="name" input="please enter name" color="0xffffff" fontStyle="bold" >
</local:myInput>
And it should trickle on down. Things can get trickier if you want to set styles individually on child components.
But, back to your original question w/ regards to properties. To keep a component encapsulated, you should create properties that set on the children. Something like this:
private var _property : String;
public function get property():String{
return _property;
}
public function set property(value:String){
_property = value;
myChildComp.property = value;
}
It can suck if you need to do this for a lot of properties. If encapsulation of this component isn't a priority; just set them in ActionScript.
In an MXML code
<fx:Script>
public var data:ArrayCollection = new ArrayCollection();
</fx:Script>
<s:DataGroup dataProvider="{data}" />
I'm getting a warning:
Data binding will not be able to detect assignments to "data"
I know that the data provider will be never changed in this case, and want to suppress this warning in this case, but I don't want to completely disable it, -show-binding-options=false in all project is not an option.
How to disable a warning only in one place? Disabling for the whole file is not so good, but acceptable.
How about just making your data variable bindable? Something like:
<fx:Script>
[Bindable]
public var data:ArrayCollection = new ArrayCollection();
</fx:Script>
<s:DataGroup dataProvider="{data}" />
Instead of using <fx:Script></fx:Script> you could use <fx:Declarations></fx:Declarations>. Any object declared in that MXML element is bindable implicitly. Here's how your code will look like then:
<fx:Declarations>
<s:ArrayCollection id="data" />
</fx:Declarations>
<s:DataGroup dataProvider="{data}" />
Additionally it becomes much more readable and there's no mix of ActionScript and MXML. Because your collection is declared as public it makes difference whether to use ActionScript with [Bindable] or using MXML.
BTW, a general recommendation for cleaner code is to separate ActionScript completely from MXML. For instance in my projects I create a separate ActionScript file for each MXML component in the form <NameOfComponent>Includes.as.
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.
How would I display a backgroundImage on a List when the List is empty?
At the moment the list is populated when items are dropped inside after a drag-and-drop but I would prefer a solution that checks for any change to the data to determine if the list is empty.
The List inherits a backgroundImage from its ScrollControlBase but what would be the best way to make it appear when the list is empty and disappear when an item is added.
Any suggestions?
Thanks!
In the past, I've done it with states for a component. Quick and dirty example would be something like this in your custom component:
<mx:List currentState="{(listItemsDataProvider.length > 0) ? 'HasItemsState' : 'NoItemsState'}">
// anything else you need
</mx:List>
and of course creating those states in the component, with the NoItemsStates changing the background image, or if your component is a container, like a Canvas, then you can have the state not display the List at all.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] public var listItems:ArrayCollection = new ArrayCollection();
private function removeAllItemsFromList():void
{
this.listItems.removeAll();
backgroundCheck()
}
private function addItemToList():void
{
this.listItems.addItem({data:null,label:"test"});
backgroundCheck()
}
private function backgroundCheck():void
{
if(this.listItems.length>0)
{
this.myList.setStyle("backgroundImage", null)
}
else
{
this.myList.setStyle("backgroundImage", "me.png")
}
}
]]>
</mx:Script>
<mx:VBox width="100%" height="100%">
<mx:List id="myList" width="100%" height="100%" backgroundImage="me.png" dataProvider="{this.listItems}"/>
<mx:HBox width="100%">
<mx:Button id="addItemButton" click="addItemToList()" label="add item"/>
<mx:Button id="removeItemsButton" click="removeAllItemsFromList()" label="remove all items"/>
</mx:HBox>
</mx:VBox>
</mx:Application>
This is how I would approach it, checking the dataProvider length. In your case you'd do so when the drop is complete.
You could extend the List control and override updateDisplayList(). Draw the backgroundImage if dataProvider.length == 0 else call super.updateDisplayList() to get normal List behavior. This will make the new List control easy to reuse if you need to.
Use the same property, set the image to null when you have some data. You may take a look at custom ItemRenderers as well.