Flex Localization: refresh DataProvider values - apache-flex

I have a ToggleButtonBar with a DataProvider setup like this:
<mx:ToggleButtonBar itemClick="clickHandler(event);" selectedIndex="0">
<mx:dataProvider>
<mx:String>{resourceManager.getString('dashboard','daily')}</mx:String>
<mx:String>{resourceManager.getString('dashboard','monthly')}</mx:String>
<mx:String>{resourceManager.getString('dashboard','quarterly')}</mx:String>
<mx:String>{resourceManager.getString('dashboard','yearly')}</mx:String>
</mx:dataProvider>
</mx:ToggleButtonBar>
To switch locale to Chinese, I have a combobox with this handler:
resourceManager.localeChain = "zh_CN";
My problem is that on locale change, while the labels for all the other controls on the screen dynamically reload for the new locale, the dataProvider values don't refresh.
I can manually reset them in code, but is there a cleaner solution?

I would abstract out the data for your data provider into a bindable variable, then just reset the data provider when you change locals.
<mx:Script>
<![CDATA[
[Bindable]
myArray:Array = new Array(
[resourceManager.getString('dashboard','daily')]
, [resourceManager.getString('dashboard','monthly')]
, [{resourceManager.getString('dashboard','quarterly')]
, [resourceManager.getString('dashboard','yearly')]);
]]>
</mx:Script>
<mx:ToggleButtonBar itemClick="clickHandler(event);"
selectedIndex="0" id="myToggleButtonBar" dataprovider="{myArray}" />
Then you can just say
myToggleButtonBar.dataProvider = myArray;
after you swap the locals and it should work.
Disclaimer, there may be some minor errors in my code, I obviously am not able to test it and I don't have flex builder available right now to even check my syntax so I hope I didn't make any spelling mistakes. But this should get you in the ballpark.

Maybe if you make a getter bindable to a custom event for ex: "langChange"
[Bindable("langChange")]
public function get dataProviderToggleB():ArrayCollection
{
var arr :ArrayCollection = new ArrayCollection();
arr.addItem(resourceManager.getString('dashboard','daily'));
arr.addItem(resourceManager.getString('dashboard','monthly'));
return arr;
}
and in your "resourceManager.localeChain" setter you dispatch:
dispatchEvent(new Event("langChange"));
and you can used like this:
<mx:ToggleButtonBar dataProvider="{dataProviderToggleB} itemClick="clickHandler(event);" selectedIndex="0">
I hope this would help you.

You should keep 'daily', ... in your array and use a labelFunction to translate the label.
When the resourceManager sends a change event you should do a combo.labelFunction = labelFunction

The trick is to add brackets around each element in the dataProvider array, that way it gets parsed correctly. Note that this also binds correctly to locale changes in flex, no custom event dispatching is needed.
<mx:ToggleButtonBar itemClick="clickHandler(event);" selectedIndex="0"
dataProvider="{[ (resourceManager.getString('dashboard','daily')),
(resourceManager.getString('dashboard','monthly')),
(resourceManager.getString('dashboard','quarterly')),
(resourceManager.getString('dashboard','yearly')) ]}">
</mx:ToggleButtonBar>

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>

How to programatically select an item in a Flex List after dataProvider is updated?

As I am somewhat new to Flex I may be missing something fundamental here. I have a Spark List container whose dataProvider is bound to a result set coming back from a RemoteObject call. Pretty standard stuff.
<s:List id="list" dataProvider="{model.stuff}" width="100%" height="100%"
selectedIndex="#{selectedSlider.value}"
itemRenderer="{stuffRenderer}">
</s:List>
The selectedIndex is associated with an HSlider, but that is not the problem. My issue is that I would like to automatically select a certain "preferred" element from the list (only initially...to guide the user).
I tried to do that in a creationComplete event but my data hadn't shown up yet...setting selectedIndex didn't work...it was too early.
What's the right way to do this?
private function findAllUsers_resultHandler(e:ResultEvent):void
{
list.dataProvider = new ArrayCollection(e.result as Array);
if(firstTry)
{
list.selectedIndex = 0;
firstTry = false;
}
}
spark.components.List has spark.components.SkinnableDataContainer in its class hierarchy which dispatches a dataProviderChanged event whenever the dataProvider changes. Unfortunatly there is no [Event] metadata in SkinnableDataContainer that allows using this event in MXML. So, you'll need to create your own custom component that extends List.
package
{
import spark.components.List;
[Event(name="dataProviderChanged", type="flash.events.Event")]
public class MyList extends List
{
public function MyList()
{
super();
}
}
}
By using your custom component you can add an event listener for dataProviderChanged and update your selectedIndex accordingly.
<ns1:MyList id="list" dataProvider="{model.stuff}" width="100%" height="100%"
dataProviderChanged="selectedIndex = selectedSlider.value"
selectedIndex="#{selectedSlider.value}"
itemRenderer="{stuffRenderer}">
</ns1:MyList>
BTW: This works with other List-based components (like DropDownList) too.
I believe it should work if you just set the initial value of the slider to the index you want to be selected at the beginning.
Something like this:
<s:List dataProvider="{yourData}" selectedIndex="{hSlider.value}" /> <s:HSlider id="hSlider" minimum="0" maximum="{yourData.length - 1}" stepSize="1" value="theIndexYouWantAsInitial" liveDragging="true" />
That should work.
HTH
FTQuest

Flex Datagrid within repeater - Data bind warning

In my Flex app I am using a repeater to show a report on my database data. On this report the user can "drill-down" on the data to show more detail. In order to make this easier on the eye I have a header label and then a datagrid within the repeater.
Whilst this works perfectly, because the dataprovider for the datagrid comes from an array in the repeaters dataprovider, it is causing the following warning:
Data binding will not be able to detect assignments to "report"
The warning is for this line:
<mx:DataGrid id="dgReport" dataProvider="{rptReport.currentItem.report}" rowCount="{rptReport.currentItem.report.length}">
Below is my code, if anyone has any suggestions for how I can get rid of the warning/do this properly they will be most welcome!
<mx:Script>
<![CDATA[
[Bindable] private var reportProvider;
private function report_Handler(event:ResultEvent):void {
// Temp variables
var currentHeader:String = "";
var previousHeader:String = "";
// Retrieve PHP array
var reportPHP:Array = ArrayUtil.toArray(event.result);
// Create Flex array
var reportFlex:Array = [];
var reportFlex_dataGrid:Array = [];
// Loop through PHP array
for(var i:int = 0; i < reportPHP.length; i++) {
// Retrieve current header
currentHeader = reportPHP[i].header;
// Clear array
if (currentHeader != previousHeader) {
reportFlex_dataGrid = [];
}
reportFlex_dataGrid.push({column1:reportPHP[i].column1, column2:reportPHP[i].column2, column3:reportPHP[i].column3});
}
// Add to repeater array
if (currentHeader != previousHeader) {
// Add to array
reportFlex.push({header:reportPHP[i].header, report:reportFlex_dataGrid});
}
// Store previous headers
previousHeader = reportPHP[i].header;
// Add to combobox data provider
reportProvider = new ArrayCollection(reportFlex);
}
]]>
</mx:Script>
<mx:Repeater id="rptReport" dataProvider="{reportProvider}">
<mx:VBox>
<mx:Spacer height="5"/>
<mx:Label id="lblHeader" text="{rptReport.currentItem.header}"/>
<mx:DataGrid id="dgReport" dataProvider="{rptReport.currentItem.report}" rowCount="{rptReport.currentItem.report.length}">
<mx:columns>
<mx:DataGridColumn headerText="Column1" dataField="column1"/>
<mx:DataGridColumn headerText="Column2" dataField="column2"/>
<mx:DataGridColumn headerText="Column3" dataField="column3"/>
</mx:columns>
</mx:DataGrid>
</mx:VBox>
</mx:Repeater>
Data binding will not be able to detect assignments to "report"
Your dataProvider is rptReport.currentItem.report. Of this, rptReport, being an mxml element, is Bindable. The currentItem property of the Repeater component is also declared to be Bindable. The report property of the current item is not bindable - current item itself is just an object. Through this warning Flex is saying that if you alter the report of an already assigned object to something else, it won't be automatically reflected in the data grid.
In most cases you can safely ignore this type of warnings.
When you say x="{a.b.c.d}" in mxml, the guarantee is that flex will detect changes made to any of the four items in the chain (a, b, c and d) and update the value of x. In other words, x will change when a or a.b or b.c or c.d is changed. For this to work, Flex expects that all those four are declared bindable. If it finds any of these items to be not bindable, it will throw a warning. A property is bindable if it was declared using mxml or if it was declared with the [Bindable] metadata tag in ActionScript.
In most cases, one would be interested only in the changes to a or a.b. In your example, changes happen only when HTTPService is resend, in which case the dataProvider itself will change.
Dude, a little off-topic, but having a grid in a repeater sounds really busy. If you want to have a drill-down, pop it up or put it in a pane that's only visible in that mode.
Visually, the repeater is a pattern which the user can internalize. A grid inside that pattern is a lot harder to deal with. Scrolling the grid vs. scrolling the repeater will likely be frustrating, let alone Tab navigation.
Logistically, you are creating a lot of in-memory UI. I would worry about performance.
Consider using a List with a custom Item renderer instead of a repeater. I still would not put a grid in there, but it's worth the effort.
Cheers

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

Problem with dispatching custom event in FLEX

Let me start off by saying that I'm pretty new to flex and action script. Anyways, I'm trying to create a custom event to take a selected employee and populate a form. The event is being dispatched (the dispatchEvent(event) is returning true) but the breakpoints set inside of the handler are never being hit.
Here is some snippets of my code (Logically Arranged for better understanding):
Employee Form Custom Component
<mx:Metadata>
[Event(name="selectEmployeeEvent", type="events.EmployeeEvent")]
<mx:Metadata>
<mx:Script>
[Bindable]
public var selectedEmployee:Employee;
</mx:Script>
...Form Fields
Application
<mx:DataGrid id="grdEmployees" width="160" height="268"
itemClick="EmployeeClicked(event);">
<custom:EmployeeForm
selectEmployeeEvent="SelectEmployeeEventHandler(event)"
selectedEmployee="{selectedEmployee}" />
<mx:Script>
[Bindable]
private var selectedEmployee:Employee;
private function EmployeeClicked(event:ListEvent):void
{
var employeeData:Employee;
employeeData = event.itemRenderer.data as Employee;
var employeeEventObject:EmployeeEvent =
new EmployeeEvent( "selectEmployeeEvent", employeeData);
var test:Boolean = dispatchEvent(employeeEventObject);
//test == true in debugger
}
private function SelectEmployeeEventHandler(event:EmployeeEvent):void
{
selectedEmployee = event.Employee; //This event never fires
}
</mx:Script>
There are a few things conspiring to cause trouble here. :)
First, this declaration in your custom component:
<mx:Metadata>
[Event(name="selectEmployeeEvent", type="events.EmployeeEvent")]
<mx:Metadata>
... announces to the compiler that your custom component dispatches this particular event, but you're actually dispatching it with your containing document, rather than your custom component -- so your event listener never gets called, because there's nothing set up to call it.
Another thing is that, in list-item-selection situations like this one, the more typical behavior is to extract the object from the selectedItem property of the currentTarget of the ListEvent (i.e., your grid -- and currentTarget rather than target, because of the way event bubbling works). In your case, the selectedItem property of the grid should contain an Employee object (assuming your grid's dataProvider is an ArrayCollection of Employee objects), so you'd reference it directly this way:
private function employeeClicked(event:ListEvent):void
{
var employee:Employee = event.currentTarget.selectedItem as Employee;
dispatchEvent(new EmployeeEvent("selectEmployeeEvent"), employee);
}
Using the itemRenderer as a source of data is sort of notoriously unreliable, since item renderers are visual elements that get reused by the framework in ways you can't always predict. It's much safer, instead, to pull the item from the event directly as I've demonstrated above -- in which case you wouldn't need an "employeeData" object at all, since you'd already have the Employee object.
Perhaps, though, it might be better to suggest an approach that's more in line with the way the framework operates -- one in which no event dispatching is necessary because of the data-binding features you get out-of-the-box with the framework. For example, in your containing document:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
[Bindable]
private var selectedEmployee:Employee;
private function dgEmployees_itemClick(event:ListEvent):void
{
selectedEmployee = event.currentTarget.selectedItem as Employee;
}
]]>
</mx:Script>
<mx:DataGrid id="dgEmployees" dataProvider="{someArrayCollectionOfEmployees}" itemClick="dgEmployees_itemClick(event)" />
<custom:EmployeeForm selectedEmployee="{selectedEmployee}" />
... you'd define a selectedEmployee member, marked as Bindable, which you set in the DataGrid's itemClick handler. That way, the binding expression you've specified on the EmployeeForm will make sure the form always has a reference to the selected employee. Then, in the form itself:
<mx:Script>
<![CDATA[
[Bindable]
[Inspectable]
public var selectedEmployee:Employee;
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="Name">
<mx:TextInput text="{selectedEmployee.name}" />
</mx:FormItem>
</mx:Form>
... you simply accept the selected employee (which is marked bindable again, to ensure the form fields always have the most recent information about that selected employee).
Does this make sense? I might be oversimplifying given that I've no familiarity with the requirements of your application, but it's such a common scenario that I figured I'd throw an alternative out there just for illustration, to give you a better understanding of the conventional way of handling it.
If for some reason you do need to dispatch a custom event, though, in addition to simply setting the selected item -- e.g., if there are other components listening for that event -- then the dispatchEvent call you've specified should be fine, so long as you understand it's the containing document that's dispatching the event, so anyone wishing to be notified of it would need to attach a specific listener to that document:
yourContainingDoc.addEventListener("selectEmployeeEvent", yourOtherComponentsSelectionHandler);
Hope it helps -- feel free to post comments and I'll keep an eye out.
You're dispatching the event from the class in the lower code sample (DataGrid, I guess), but the event is defined in the EmployeeForm class, and the event handler is likewise attached to the EmployeeForm instance. To the best of my knowledge, events propagate up in the hierarchy, not down, so when you fire the event from the form's parent, it will never trigger handlers in the form itself. Either fire the event from the form or attach the event handler to the parent component.

Resources