Is it possible to specify individual fonts in Flex DropDownList? - apache-flex

I have just implemented a dropdownlist of checkboxes taken from this ComboCheck example but made it extend DropDownList instead of ComboBox to provide better functionality that I required. I am attempting to create a DropDownList where some items are bold and non-checkboxes (or can be checkboxes) and others are not.
I have not been able to find anything online about doing this yet and have been trying to figure it out. I am currently using an ArrayCollection as a dataProvider but I think this could possibly be my issue and I should be trying to setup the labels in flex not AS3.
Does anyone know if this is possible? And if so do they have any links that could possibly help point me in the right direction?
Thanks.
EDIT: Code added for the itemRenderer, this worked I just need to specify each item that I want to be bold, though is there a better way to do this in the flex code as opposed to checking for a matching string in the renderer?
public class ComboCheckItemRenderer extends ItemRenderer{
public var item:CheckBox;
public function ComboCheckItemRenderer(){
super();
item = new CheckBox();
item.x = 5;
addElement(item);
item.addEventListener(MouseEvent.CLICK, onClick);
}
private var _data:Object;
[Bindable]override public function set data (value:Object):void {
if (value!=null) {
_data = value;
item.label = value.label;
if(item.label == "item1"){
item.setStyle("color","0x00ff00");
item.setStyle("fontWeight","bold");
}
item.selected = value.selected;
}
}
Edit 2: What I am ultimately trying to do is create a dropdown of checkboxes with data that I obtain from blazeDS that basically has a bunch of group titles and their corresponding sub-elements. I am trying to have the dropdown make the groups be in bold and to the left, and their sub-elements normal font and offset to the right. I also need to know when they are clicked whether it was a group header or sub-element, so that I can add them to an object that I will be sending back to my service to perform a sql query on.
ie.
[ ]**GROUP**
[ ] element
[ ] element
[ ]**GROUP**
[ ] element

What does your data look like? Why aren't you using MXML for this? Why are you overriding set data() as opposed to hooking the dataChange event? You are writing way more code than you need to here.
Lets look at it in a more "Flexy" way. Notice how I am using data binding for everything and conditionally setting the fontWeight based on the data that comes in. Anything more complicated should bust out to a function in the Script tag.
<?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">
<fx:Script>
<![CDATA[
protected function onClick(event:MouseEvent):void {
}
]]>
</fx:Script>
<s:CheckBox x="5" click="onClick(event)"
label="{data.label}" selected="#{data.selected}"
fontWeight="{data.label == 'item1' ? 'bold' : 'normal'}"/>
</s:ItemRenderer>
In light of your question you added in your edit, I would ask: What criteria are you using? You can put any function in your binding expression, so at a very minimum, you might do something like this:
<fx:Script>
<![CDATA[
private var itemsToBold:Array = ["label1", "label2"];
private function getFontWeight(label):String {
if(itemsToBold.indexOf(label) > 0)
return "bold";
return "normal";
}
]]>
</fx:Script>
<s:CheckBox fontWeight="{getFontWeight(data.label)}"/>

Related

Using a ComboBox as ItemEditor in Flex 4

I have a simple DataGrid with data. Of one of the columns, I want to use a ComboBox to edit the field, instead of the standard edit box.
How do I do that? I have tried all kind of things I found on the internet, but they all fail in simply updating the value. I'd say it shouldn't be too hard to do this.
I'm actually in the process of doing this myself, and with the spark:DataGrid it actually gets a bit easier than halo - but both follow the same setup / architecture.
Start with:
spark.components.gridClasses.ComboBoxGridItemEditor;
Depending on the nature of your data setup and/or how prolific this kind of editing will be for your application, you can write it inline as most documentation will suggest within a <fx:component>, or simply subclass this (although behind the scenes these are the same thing - the later being much easier to reuse). The data for the combo in my scenario is a sub selection of a bigger parent object, so I chose to make it easier on myself and add an additional property dataField to mimic other renderer / editors - in what actually shows in just the cell itself (when not in editing mode).
A basic setup looks something more or less like this (at least mine does):
public class AccountComboEditor extends ComboBoxGridItemEditor
{
private _dataField:String;
public function AccountComboEditor()
{
super();
//note - typically you wouldn't do "logic" in the view but it's simplified as an example
addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}
public function get dataField():String { return _dataField; }
public function set dataField(value:String):void
{
if (_dataField !=value) //dosomeadditionalvalidation();
_dataField = value;
}
override public function prepare():void
{
super.prepare();
if (data && dataField && comboBox) comboBox.labelField = data[dataField];
}
protected function onCreationComplete(event:FlexEvent):void
{
//now setup the dataProvider to your combo box -
//as a simple example mine comse out of a model
dataProvider = model.getCollection();
//this isn't done yet though - now you need a listener on the combo to know
//what item was selected, and then get that data_item (label) back onto this
//editor so it has something to show when the combo itself isn't in
//editor mode
}
}
So the real take away is to setup the labelField of the combobox, either internally in the subclass or externally if you need to expose it as an additional property.
The next part is to use this as part of the mx.core.ClassFactory for the actual data grid. A simple view would look like something similar:
<s:DataGrid>
<fx:Script>
private function getMyEditor(dataField:String):ClassFactory
{
var cf:ClassFactory = new ClassFactory(AccountComboEditor);
cf.properties = {dataField : dataField };
return cf;
}
</fx:Script>
<s:columns>
<mx:ArrayList>
<s:GridColumn itemEditor="{getMyEditor('some_data_property')}" />
</mx:ArrayList>
</s:columns>
</s:DataGrid>
This Creating item renderers... doc will give you more info.
I figured it out. I just wanted a simple drop down box, instead of a text-editing field.
The following code does want I want:
<mx:DataGridColumn dataField="type" headerText="Type" editorDataField="value">
<mx:itemEditor>
<fx:Component>
<mx:ComboBox>
<mx:dataProvider>
<fx:String>Gauge</fx:String>
<fx:String>Graph</fx:String>
<fx:String>Indicator</fx:String>
</mx:dataProvider>
</mx:ComboBox>
</fx:Component>
</mx:itemEditor>
</mx:DataGridColumn>

Flex 4 Custom Component - How to notify skin of property changes?

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

Initialize properties of custom component before creating children in flex

Say I have the following custom component:
<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 prop:String;
private function formatProp() : String {
return "Hello, " + prop;
}
]]>
</fx:Script>
<s:Label text="User: {prop}"/>
<s:Label text="Greeting: {formatProp()}"/>
</s:Group>
If I add it to my application like this:
<local:MyComponent prop="Hello"/>
The result looks like:
User: Mark
Greeting: Hello, null
It seems Flex is setting prop on my custom component after it has already initialized the child labels, so it's reliant on the property changed event to set the user label.
Is there an elegant way to make Flex wait for all of my component's properties to be set before initially evaluating bindings?
Note: I realize the formatProp function is trivial and could be included inline, but this is just a simplified example.
The "elegant way" would be to actually provide data binding, so that you can change your property also afterwards. Your initial idea looked good, working with the answer provided by Cornel. I just wanted to mention this as your actual question sounded more like that you know your data binding is not working and you just wanted to postpone the initial setting of the variable.
Btw, should you plan to create custom components in Actionscript (instead of mxml) you'll face the opposite problem: properties are set before you had a chance to actually create your children, so you may need to buffer them if they actually should influence some childs properties.
it is not related to component livecycle, more to binding rules. Your function "formatProp" should recieve the parameter "prop" as a parameter in order to be called when the prop is changed. Try this code:
private function formatProp(props:String) : String {
return "Hello, " + props;
}
<s:Label text="Greeting: {formatProp(prop)}"/>

Flex: image switching place in tilelist

I'm running into an odd issue with itemRenderers inside a TileList.
Here is a working example without an itemRenderer: 152.org/flex/
Here is the broken version with an itemRenderer: 152.org/brokenExample/
(I don't have the rep to make both of these a link)
Both examples have "View Source" enabled.
To see the problem use the broken example, select an album and scroll down one row. Scroll back up and the images will be switched. If you try this on the working example it's fine.
This seems to be a widely known bug, but I can't find a solution for it.
UPDATE
I started playing with this example again and found out something else. Turns out you don't have to override the data setter. You can create a new method in the itemRenderer that is set whenever the tile wants to refresh. So the trick is to not rely on the initialize or creationComplete methods.
This is what I have for the itemRenderer in the Application.
<itemRenderers:ImageTile img="{data}"/>
This is the code I have in the itemRenderer.
public function set img(value:String) : void {
trace("setting source: " + value);
this.source = value;
this.name = value.toString().split("/").pop().split(".").shift();
}
I updated my example to reflex this change.
I don't have your app handy, so I can't test end-to-end, but I've looked at your source. You probably need to override the data setter in your itemRenderer:
<?xml version="1.0" encoding="utf-8"?>
<mx:Image xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
this.source = data;
this.name = data.toString().split("/").pop().split(".").shift();
}
private function init() : void {
// Removed from your source and transplanted above
}
]]>
</mx:Script>
</mx:Image>
Flex will attempt to re-use item renderers in lists (which means the lifecycle events you might be expecting -- initialize, creationComplete, etc. -- won't always fire), so if you want to be sure your renderer gets updated when the data item changes (as it will when scroll events happen), the best practice is to override the renderer's data property. That'll most likely fix the problem.
Maybe try to invalidate on creationComplete?
From what I recall with DataGrids (which work somewhat similarly to a tilelist), when an item comes into focus its recreated.
<mx:itemRenderer>
<mx:Image id="myImage" creationComplete="myImage.invalidate()" />
</mx:itemRenderer>
Haven't tried this code but I think this is where you want to start looking. I took a look at your itemRenderer component. Try creationComplete instead of initialize to call your function

Flex drag and drop reorder of a HorizontalList control - Finding FROM and TO indexes?

I have a Flex3 project with 2 HorizontalList controls; one of which has drag & drop enabled. Both controls will always have the same number of items, and are related... index 0 from the 1st control matches index 0 from the 2nd control, and so on.
Naturally, when using drag and drop to reorder the 2nd control, I want the items in the 1st control to reflect the same reordering. I think I can handle the actual updating of the control, but I'm having trouble finding the from and to indexes of the item that has been moved via drag & drop.
Using the dragDrop method on the control that allows drag and drop, and perusing the contents of event.currentTarget (instead of event.target because the docs say so) in the debugger shows a public property named selectedIndex which does seem to hold the from index, but I don't see any properties that will tell me the to index.
Am I overlooking the property? Do I have to infer it based on something else? Is there a better way to do this?
update: An acceptable solution might also be to iterate over the contents of the HorizontalList and save the internal index value to an array that could then be used to re-sort the dataProvider for the other list; but I can't figure out how to iterate over the list contents, so I'm just as stuck. Any help there?
Here's my code:
main.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:com="components.*"
layout="absolute"
>
<mx:Script>
<![CDATA[
import mx.events.DragEvent;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
//these two variables are junk data to represent
//what we will get back from our data services...
[Bindable]
private var btnData:ArrayCollection = new ArrayCollection(
[
{title:'title1', index:0},
{title:'title2', index:1},
{title:'title3', index:2},
{title:'title4', index:3}
]
);
[Bindable]
private var chainData:ArrayCollection = new ArrayCollection(
[
{tmp:'testing 1'},
{tmp:'testing 2'},
{tmp:'testing 3'},
{tmp:'testing 4'}
]
);
//handle button re-ordering
private function handleBtnReorder(event:DragEvent):void{
Alert.show(event.action.toString());
}
]]>
</mx:Script>
<mx:HorizontalList
id="ChainView"
dataProvider="{chainData}"
itemRenderer="components.ItemRenderers.ChainLinkRenderer"
left="20"
right="20"
top="20"
height="300"
rowHeight="300"
columnWidth="500"
rollOverColor="#ffffff"
selectionColor="#eeeeee"
/>
<mx:HorizontalList
id="btnList"
dataProvider="{btnData}"
dragEnabled="true"
dropEnabled="true"
dragMoveEnabled="true"
dragDrop="handleBtnReorder(event)"
itemRenderer="components.ItemRenderers.BtnListRenderer"
horizontalCenter="true"
left="20"
right="20"
top="320"
height="50"
rowHeight="35"
rollOverColor="#ffffff"
selectionColor="#ffffff"
/>
</mx:Application>
The Alert.show(...) isn't there to show anything useful, I've set a breakpoint on that line so that I can inspect the event argument.
I don't think the custom itemRenderer's are important (there's not much code to them, yet), so I'll leave them out for brevity's sake. If you think they are important, let me know and I'll edit them in here.
It turns out the solution was to switch from using the dragDrop event to the dragComplete event; at which point, the binded dataProvider has been updated to reflect the reordering.
Thats why the Model Locator pattern is made for!
You must move your dataProvider to a singleton class (Model for instance) than your item renderers can easily access to it. There is no point that your item has the property Index since its already given by a [ArrayCollection].getItemIndex(..)

Resources