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.
Related
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>
I want to put a textinput box on the header of a datagrid in flex, whose values changes dynamically according to the no of data or value present in that perticular column.
Please any one help me, am posting the image acctually what I want.
Thanks in advance.
I'll assume you can use Spark DataGrid. In that case you just need to create a custom headerRenderer. To keep things simple we'll start from the default headerRenderer. In your Flex SDK sources, find the class spark.skins.spark.DefaultGridHeaderRenderer. Copy this file into your project and rename it appropriately.
Inside this class, find the components labelDisplayGroup and sortIndicatorGroup (they're at the bottom). They're inside an HGroup, so we can simply add our counter component in between.
<!-- I removed the original comments for brevity -->
<s:HGroup left="7" right="7" top="5" bottom="5" gap="2" verticalAlign="middle">
<s:Group id="labelDisplayGroup" width="100%" />
<!-- our counter component -->
<s:Label id="numRowsDisplay" />
<s:Group id="sortIndicatorGroup" includeInLayout="false" />
</s:HGroup>
So far for the visual component; now we have to update its text property appropriately. In the script block add the following snippet:
private var dp:IList;
override public function set owner(value:DisplayObjectContainer):void {
if (dp) dp.removeEventListener(CollectionEvent.COLLECTION_CHANGE, updateNumRows);
if (super.owner) super.owner.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange);
super.owner = value;
dp = value ? DataGrid(value).dataProvider : null;
updateNumRows();
if (dp) dp.addEventListener(CollectionEvent.COLLECTION_CHANGE, updateNumRows);
if (value) value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange);
}
private function onPropertyChange(event:PropertyChangeEvent):void {
if (event.property == 'dataProvider') {
dp = event.newValue as IList;
updateNumRows();
}
}
private function updateNumRows(event:CollectionEvent=null):void {
numRowsDisplay.text = (dp ? dp.length : 0) + "";
}
What happens here? the owner property of the renderer refers to the data component that holds this renderer; in this case a DataGrid. So when the owner is assigned to the renderer, we access its dataProvider and use its length to update the counter component.
So what are those listeners for? There are two cases you might want to foresee.
The number of items in the dataprovider changes: that's why we listen on the dataprovider for CollectionChange events.
The entire dataProvider is changed or nullified: for this we listen for PropertyChangeEvents on the DataGrid and update the counter only when the 'dataProvider' property changes
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;
}
I've made a component based on a mx:TitleWindow that contains linkbuttons that I'm using as a context-menu.
The TitleWindow component contains link buttons like this:
[Bindable]
private var _showEmailThis:Boolean = false;
[Bindable]
private var _showApproveThis:Boolean = false;
[Bindable]
private var _showReviewThis:Boolean = false;
<mx:LinkButton id="lnkEmailThis"
visible="{_showEmailThis}"
includeInLayout="{_showEmailThis}"
click="lnkEmailThis_click()"
label="Email this!" />
<mx:LinkButton id="lnkApproveThis"
visible="{_showApproveThis}"
includeInLayout="{_showApproveThis}"
click="lnkApproveThis_click()"
label="Approve this!" />
<mx:LinkButton id="lnkReviewThis"
visible="{_showReviewThis}"
includeInLayout="{_showReviewThis}"
click="lnkReviewThis_click()"
label="Review this!" />
This component contains a large number of links and is re-used by multiple modules in the same application.
Within each module, this component is used when a user clicks on a row in a datagrid.
The code looks like this:
In "Requests" module:
private function dgRequests_click(event:MouseEvent):void
{
menu.showApproveThis = true;
menu.showReviewThis = true;
}
In "Performance" module:
private function dgPerformance_click(event:MouseEvent):void
{
menu.showEmailThis = true;
menu.showReviewThis = true;
}
As you can see, the visibility of individual linkbuttons is controlled within each module by setting boolean properties.
If I alter this component to use a renderer, how can I control the visibility of the linkbuttons from the code in each module (there are over a hundred links with different functionality - not every link will be used in each module)? Note: it is not known from the back-end which grid within which module uses which link. This is set in the front end within each module's actionscript file.
e.g., if the XML looks like this:
<links>
<link>
<label>Email This</label>
<visible>_showEmailThis</visible>
</link>
<link>
<label>Approve This</label>
<visible>_showApproveThis</visible>
</link>
<link>
<label>Review This</label>
<visible>_showReviewThis</visible>
</link>
</links>
and I set the Email link to show in the module like this:
private function dgPerformance_click(event:MouseEvent):void
{
menu.showEmailThis = true;
}
How do I make it take effect? Is there a way to control the visibility of a linkbutton within an item renderer in a component from the module that uses that component?
I'm not really sure I understand your question right. But if you have a component (or a group of 'em) sequentially repeating several times with different properties' values you should use mx:Repeater.
Adobe's docs on Repeater
There is also a live example in TourDeFlex (which I strongly recommend to install and use).
Looks like this is not possible.
I have a flex MXML UI that is building a set of radio buttons using the Repeater component:
<mx:Repeater id="radios"
dataProvider="{_lists.(#id == _question.single.#response_list).group.listItem}">
<mx:RadioButton groupName="responses"
label="{radios.currentItem.#text}"
data="{radios.currentItem.#level}"/>
</mx:Repeater>
What I want to do is have the component within the repeater -- the RadioButton in this example -- be chosen based on the value of a property of radios.currentItem: If the value of currentItem is "foo", for example, I want a Button there, or if it's "bar" I want a RadioButton. Is it possible to perform this kind of conditional construction in an MXML component, or must I revert to ActionScript to do it?
I'm thinking of something along these lines:
<mx:Repeater id="r" dataProvider="{list}">
<mx:If test="{r.currentItem.#type == 'radio'}">
<mx:RadioButton label="{r.currentItem.#text}" />
</mx:If>
<mx:If test="{r.currentItem.#type == 'specify'}">
<custom:Specify label="{r.currentItem.#text}" />
</mx:If>
</mx:Repeater>
The right (and really only sensible) way to do it would be with a plain ol' for loop and ActionScript:
for each (var o:Object in yourDataProvider)
{
if (o.someProperty)
{
var rb:RadioButton = new RadioButton();
yourContainer.addChild(rb);
}
else
{
var s:Specify = new Specify();
yourContainer.addChild(s);
}
}
You could do as slashnick suggests, and just add both components with each iteration of the Repeater, toggling their display based on a test of some sort (in which case I'd probably suggest including the includeInLayout attribute as well), but you'd be bloating your display list by doing so, and it doesn't scale -- eventually, you just end up doing it in ActionScript anyway.
I think you have to use action script for any conditions. An conditional statement doesn't seem to exist in mxml. Although you could include both elements and use inline as to set the visible state.
<mx:Repeater id="r" dataProvider="{list}">
<mx:RadioButton label="{r.currentItem.#text}" visible="{r.currentItem.#type == 'radio'}" />
<custom:Specify label="{r.currentItem.#text}" visible="{r.currentItem.#type == 'specify'}" />
</mx:Repeater>
See http://www.firemoss.com/post.cfm/Powerful-MXML-Bindings-with-Ternary--Operators for more examples.
I would use AS3 for this. My opinion is that its best to use mxml for the display and AS3 for the logic... similar to how in .Net you have the code-behind