I have 2 TileList component in my Flex application.
1 tilelist is filled with data much like following xml sample:
<person name="Test">
<likes>Flex</likes>
<likes>PHP</likes>
</person>
<person name="test2">
<likes>HTML</likes>
<likes>CSS</likes>
</person>
the data shown in that tilelist is the name.
my second tilelist:
<items>
<preference>Flex</preference>
<preference>Flash</preference>
<preference>HTML</preference>
<preference>CSS</preference>
<preference>PHP</preference>
<preference>CMS</preference>
<preference>ASP</preference>
<preference>C</preference>
</items>
data shown is the preference.
The user can click the first tilelist and then the items that person "likes" should be selected in the second tilelist (in other words they lit up).
click event on my first tilelist
private function highlightPreferences(e:ListEvent):void{
trace(e.currentTarget);
//and now I'm stuck
}
Is there any way to achieve this?
Just write a function that returns the selectedIndices for a particular person. Then, bind the second TileList's selectedIndices like this: selectedIndices="{findLikes(firstList.selectedItem)}" The binding will fire if firstList.selectedItem changes.
Oh, and please don't use a repeater. Lists can do everything repeater can better.
Related
I have an AdvancedDatagrid in my Flex application.
<mx:AdvancedDataGrid id="reportGrid" creationComplete="groupedData.refresh()" width="100%" height="100%" variableRowHeight="true">
<mx:dataProvider>
<mx:GroupingCollection2 id="groupedData" source="{reportData}"/>
</mx:dataProvider>
</mx:AdvancedDataGrid>
I dynamically assign columns and grouping and summaries to groupedData GroupingCollection2.
When I filter the datasource and call groupedData.refresh() the grid refreshes fine. But when I load data, and apply no grouping (add no groupings to the GroupingCollection2), the groupedData.Refresh() does not update the grid to show only the filtered in rows. I have also tried calling the grid's own InvalidateList(), to no avail.
Thanks for the suggestion.
I looked inside the GroupingCollection2.as:
// return if no grouping or groupingFields are supplied
if (!grouping || grouping.fields.length < 1 )
{
super.source = source;
// dispatch collection change event of kind reset.
resetEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
resetEvent.kind = CollectionEventKind.RESET;
dispatchEvent(resetEvent);
return true;
}
So for some reason Adobe does reset the dataSource if there is no grouping
on, which (in my opinion) is a bug, or a bad assumption.
The code above gets called when calling the groupingCollection.refresh(),
which is the only way of refreshing the display on the AdvancedDataGrid (that I am aware of)
So, I presume a workaround would be to always have at least 1 grouping
on the AdvancedDataGrid. A bit of an undesirable restriction, though.
My guess is this is happening because the filterFunction that was being applied to the old ArrayCollection is getting wiped out when the data gets loaded. What I would do is make a copy of the old ArrayCollection's filterFunction (and Sort if needed) and then reassign those properties once the data has been loaded.
Here's a quick (and untested) example:
public function loadData(myData:ArrayCollection):void
{
var filter:Function = reportData.filterFunction;
reportData = myData;
reportData.filterFunction = filter;
reportData.refresh();
}
I have a project that has 4 views where I'm using the tabBar with viewStack/NavigatorContent. I have a couple HTTPServices set up gathering the XML and converting to Bindable ArrayCollections. When and how is the best way to pass that data to such things as charts, dataGrids, etc. that aren't seen until the user clicks a tab? Right now I have each item set up with creationComplete functions that pass it then. Although it seems to be working, is this the best way, or is there something better and maybe even quicker?
Thanks,
Mark
The best way is using data binding. Say you have a main container which contains ViewStack witch components representing tabs content. So you should have [Bindable] properties for data in a main container like the following:
[Bindable]
private var chartData:ArrayCollection;
[Bindable]
private var dataGridData:ArrayCollection;
etc.
So for the component, containing chart, you should populate chart data with data binding:
<ViewStack>
…
<MyChartsTab chartData="{chartData}" />
…
</ViewStack>
And of course you should introduce the same chartData field (make sure it is public) in your MyChartsTab component. Your charts there can be populated with data binding too.
So after getting data you just fill your fields in main component and data binding performs the rest job without any care of initialization from your side.
When creating your views, make sure you allow a public variable (like 'dataProvider') where you can bind the data it needs. Like this:
<mx:ViewStack>
<s:NavigatorContent>
<SomeComponent dataProvider="{someData}" />
</s:NavigatorContent>
<s:NavigatorContent>
<SomeComponent2 dataProvider="{someData}" />
</s:NavigatorContent>
</mx:ViewStack>
And within the custom component you'd have:
private var _data:Object; // use strong typing if possible
public function set dataProvider(value:Object):void // use strong typing if possible
{
this._data = value;
}
public function get dataProvider():Object
{
return this._data;
}
In Flex 4, I have a Spark List component with item renderers.
I would like to select an item in the List by clicking on it, and deselect it also, by clicking on the same selected item. Like an on/off switch.
My item renderer has the following states:
<s:states>
<s:State name="normal"/>
<s:State name="hovered"/>
<s:State name="selected"/>
</s:states>
so I tried to add a click event listener to the item renderer with:
private function selectUnSelect():void {
if (currentState == 'selected') currentState = 'normal';
else currentState = 'selected';
}
with an awkward behaviour... where the item stay selected even after clicking it again in the selected state.
Think of using the List component without the Command (on mac) or the Control button on windows.
This blog post might be useful to you: http://flexponential.com/2009/12/13/multiple-selection-in-a-spark-list-without-the-control-key/
Rather than have the item renderer set its state, I would operate on the List itself. Have your item renderer dispatch an event when it is clicked that includes the data of the item renderer, then add a listener for that event (either in a component that extends List, or in the component that contains your list). You can then check if the data matches any of your List's selectedItems. If not, append the item to the selectedItems. If so, remove it from the selectedItems. Hope that helps.
I want to create a data grid that has 4 columns: description, quantity, price, and line total. The line total will simply be the product of quantity & price. I want the last line of the grid to be the total of all line totals.
How can I create this type of grid? I thought of using item renderers, but I can't figure out how to have the last row be for the line items total.
If I must create a custom component, I'd appreciate book recommendations on custom component creation. While I have a general understanding of how to create custom components, I don't have a firm a grip on it as I'd like. Thanks.
While you should definitely check out the links provided by www.Flextras.com, there are a couple of ways to provide the functionliaty you're after:
The simplest is a labelFunction:
<DataGrid dataProvider="{dp}">
<columns>
<DataGridColumn labelFunction="sumTotals" />
</columns>
</DataGrid>
private function sumTotals(item:Object,column:DataGridColumn):String {
return Number(item.quantity * item.price).toString();
}
Alternatively, you could create your own itemRenderer, as follows:
<!-- MyItemRenderer.mxml -->
<mx:Label>
<mx:Script>
override public function set data(value:Object):void {
super.data = value;
this.label = Number(item.quantity * item.price).toString();
}
</mx:Script>
</mx:Label>
<!-- Your component -->
<DataGrid dataProvider="{dp}">
<columns>
<DataGridColumn itemRenderer="MyItemRenderer"/>
</columns>
</DataGrid>
A Flex DataGrid cannot include items that are not in the dataProvider, so adding a row that includes the total of all line items in the DataGrid is not practical, without extending the DataGrid.
Take a look at this documentation for info about creating custom components. Also take a look at this screencast series. A direct link to episode 1.
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