<mx:DataGrid x="10" y="10" width="180" height="302" id="dgActions" dataProvider="{actionCollection}">
<mx:columns>
<mx:DataGridColumn headerText="Action" dataField="name"/>
<mx:DataGridColumn headerText="" dataField="setting" width="30" rendererIsEditor="true">
<mx:itemRenderer >
<mx:Component>
<mx:Box width="100%" height="100%" horizontalAlign="center" verticalAlign="middle">
<mx:CheckBox selected="{data.setting}" click="setActionSetting()">
<mx:Script>
<![CDATA[
private function setActionSetting(){
data.setting = String(this.selected);
}
]]>
</mx:Script>
</mx:CheckBox>
</mx:Box>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
For some reason I'm getting an error at the data.setting= String(this.selected) line which says "Access to possibly indefined property selected through a reference with static type".
[edit] The solution to the above problem (albeit not the entire mess) was that once you're inside a <mx:Component> tag you are within the scope of said component. To get access to the script and nodes outside this component you have to use the outerDocument object.
[end edit]
I'm not sure what it's expecting, I'm assuming that it's going to pass the true/false value of the selected(ness) of the checkbox into the method, but it appears not to understand what "this" is, in this context.
Am I doing something obviously wrong? All I want is for the data source to reflect the change in the status that it initially fed into the checkbox.
EDIT:
I just noticed that when I add trace('foo') to the function, it never writes anything back to the console. Is the checkbox's native behavior (and event capture) preventing it from bubbling past to my function?
Additionally, when I add references to objects outside in the rest of the document, it tells me it doesn't recognize them. I'm totally confused as to how Flex scopes things... any additional guidance or links to reference would be really handy.
this in this (ha) case is referring to the component renderer and not the surrounding class (or the checkbox, datagridcolumn, datagrid, etc). You are really better off breaking the renderer out into a real component so you won't be obfuscating the scope as much as when the inline component approach is used.
Peter Ent's series on itemRenderers is extremely useful and should explain everything you want to know on the subject.
If I had to guess "this" is the mx:Script element, try "parent.selected".
CheckBox.selected requires a Boolean value. The fact that you're setting data.setting to a String value tells me that data.setting is NOT a Boolean.
So, after a great deal of agony I have finally figured out how this all works....
Joel is on the right track, this doesn't refer to what you would hope it would refer to (namely the checkbox). Additionally, even if you pass this into the method FROM the checkbox node, it refers to the parent wrapper class and not the checkbox itself. So, the solution is to pass in the event, and then access its target, which FINALLY is the checkbox. And then you're home.
In other words...
<mx:CheckBox selected="{data.setting}" click="setActionSetting(event)">
<mx:Script>
<![CDATA[
private function setActionSetting(e:Event):void{
data.setting = e.target.selected;
trace("n=" + data.name + " set to " + data.setting);
//the name is the other piece of the data that I omitted for clarity
}
]]>
</mx:Script>
</mx:CheckBox>
Related
First of all I wanted to thank in advance to everyone that reads this post.
I'm having a problem when adding a child to a VBox component. Is it not the same thing?:
List Item:
<mx:Script>
<![CDATA[
public function addChildren():void {
var f:FaqItem=new FaqItem();
f.id="newUsersAssistance";
this.cont.addChild(f);
}
]]>
</mx:Script>
<mx:VBox id="cont" width="100%" borderThickness="0" verticalGap="0"/>
and:
<mx:VBox id="cont" width="100%" borderThickness="0" verticalGap="0">
<view:FaqItem id="newUsersAssistance" />
</mx:VBox>
I am injecting two properties (question and answer) to the FaqItem component from an auxiliar file (using BabelFX) that depends on the id of the FaqItem, but it is only working when I choose the second option. If I use the first option, I get a child but with the text in question and answer fields is empty. I need to use the first option.
Is there anything I am doing wrong?
Thanks again for everything
Kind Regards
i don't think you will be able use the id property of the dynamically added component to do Injection. I suggest you keep some bindable variables to bind the value to the dynamic FaqItem.
I have a list that uses a checkbox itemrenderer. The dataprovider is a collection of people. When I load the data from a file, each list item shows the person's name (last, first -- labelFunction), and the checkbox's selected property shows the person's included property. I.e.,
Smith, Doug - [x]
Williams, Bob - [ ]
Morris, Anne - [x]
However, each person also has an active property. I want to disable the checkbox for people who are inactive (meaning, "you can't include inactive people"). I have tried several methods to get this to work, including what's suggested here http://forums.adobe.com/thread/416786 to do the same thing in a datagrid. However, none of them work and all the checkboxes are enabled regardless of the person's active status. Here is my basic code:
<mx:List id="peopleIncludedList"
dataProvider="{someProvider}"
labelFunction="peopleLabelFunction">
<mx:itemRenderer>
<mx:Component>
<mx:CheckBox change="onChange(event)"
selected="{outerDocument.isIncluded(data)}">
<mx:Script>
<![CDATA[
private function onChange(e:Event):void
{
...
}
]]>
</mx:Script>
</mx:CheckBox>
</mx:Component>
</mx:itemRenderer>
</mx:List>
Any help on this would be greatly appreciated. Thank you.
-- Ian
I'll take a crack at it, but sometimes it is hard to tell without sample data.
First, don't reference the outerDocument in arenderer, and don't use binding, instead listen to the dataChange event
<mx:List id="peopleIncludedList"
dataProvider="{someProvider}"
labelFunction="peopleLabelFunction">
<mx:itemRenderer>
<mx:Component>
<mx:CheckBox change="onChange(event)" dataChange="onDataChange()">
<mx:Script>
<![CDATA[
private function onChange(e:Event):void
{
// not sure what this method is doing
}
private function onDataChange():void{
this.selected = isIncluded(data); // whatever your processing is
if(data.person.active == true){
this.enabled = true;
} else {
this.enabled = false;
}
}
]]>
</mx:Script>
</mx:CheckBox>
</mx:Component>
</mx:itemRenderer>
</mx:List>
Since stackoverflow only notifies me daily of new answers, I wasn't actively looking at this thread, and was more involved on the Adobe boards. Anyway, found a solution. Reference it here http://forums.adobe.com/message/3267367. Thanks everyone for your suggestions and helpfulness!
I am using a custom itemEditor for my DataGrid.
The itemEditor has a simple TextField as a component.
But when ever i am clicking the cell i am getting an error as :
ReferenceError: Error #1069: Property text not found on editors.customItemEditor and there is no default value.
at mx.controls::DataGrid/itemEditorItemEditEndHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\DataGrid.as:4827]
Plz help me to resolve this issue.
My Opinion is that the error is coming for the "text" field. But i havn't accessed "text" field or used it anywhere in my code.
Regards,
Ravi
i solved the problem with commiting incluting "return data["selected"].toString()" into get text:
<mx:DataGridColumn dataField="selected" rendererIsEditor="true" >
<mx:itemRenderer>
<fx:Component>
<mx:Box styleName="" width="100%" height="100%" backgroundAlpha="0.0"
horizontalAlign="center" verticalAlign="middle">
<fx:Script>
<![CDATA[
public function get text():String
{
return data["selected"].toString();
}
public function set text(value:String):void
{
}
protected function checkbox1_clickHandler(event:MouseEvent):void
{
data["selected"]=event.target["selected"];
}
]]>
</fx:Script>
<mx:CheckBox selected="{data.selected}" click="checkbox1_clickHandler(event)"/>
</mx:Box>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
Taken from http://livedocs.adobe.com/flex/3/html/help.html?content=celleditor_8.html
By default, Flex expects an item editor to return a single value to the list-based control. You use the editorDataField property of the list-based control to specify the property of the item editor that contains the new data. Flex converts the value to the appropriate data type for the cell.
The default item editor is a TextInput control. Therefore, the default value of the editorDataField property is "text", which corresponds to the text property of the TextInput control. If you specify a custom item editor, you also set the editorDataField property to the appropriate property of the item editor
I am still Struck in this error but i think there is a hope of coming out of it.... :)
If we take TextInput as the itemEditor, like:
dataGridColumn.itemEditor = new ClassFactory(TextInput);
Then there was no problem as "text" is being internally defined inside TextInput.as.
On a similar note if i copy set text and get text inside our custom Editor, then its working fine.. only problem that is
coming is regarding commiting the changed values.
I am working on it, hope i'll be able to sort it out.
P.S.: I am a newbie here, so plz pardon me of any idiotic RnD... :p
I have a Flex question. I was wondering, if I have two list boxes, and I want to drag and drop between both of them, how do I prevent the user from dragging onto the same list (thus duplicating the item? I cannot have a situation where that is the case. Thanks guys
Haven't tested it but I guess something like this should work:
Listen to dragStart event on both lists and set a source variable depending on event.target. Now listen to the dragDrop event on both lists and call event.preventDefault() if the source is same as the target.
Here are some simple functions I made while building a working application I used to wrap my head around flex drag and drop. I was looking for a way to have multiple lists with drag and drop functionality that would not interfere with each other. Also, I didn't want to deal with copying list data around.
private function onlyAllowMoveDragOverHandler(event:DragEvent):void {
event.preventDefault();
event.currentTarget.showDropFeedback(event);
DragManager.showFeedback(DragManager.MOVE);
}
private function allowDropOnlyIfInitiatorEqualsComponent(event:DragEvent, component:IUIComponent):void {
event.preventDefault();
if (event.dragInitiator == component) {
DragManager.acceptDragDrop(event.target as IUIComponent);
}
else {
DragManager.showFeedback(DragManager.NONE);
}
}
And I in use in my mxml:
<mx:List
x="10"
y="170"
id="availableLangsList"
dataProvider="{availableLangs}"
width="100"
height="200"
dragEnabled="true"
dragMoveEnabled="true"
dropEnabled="true"
dragOver="onlyAllowMoveDragOverHandler(event);"
dragEnter="allowDropOnlyIfInitiatorEqualsComponent(event, selectedLangsList);"
dragComplete="selectedLangs.refresh();"
/>
<mx:Label x="129" y="153" text="list 4"/>
<mx:List
x="129"
y="170"
id="selectedLangsList"
dataProvider="{selectedLangs}"
width="100"
height="200"
dragEnabled="true"
dragMoveEnabled="true"
dropEnabled="true"
dragOver="onlyAllowMoveDragOverHandler(event);"
dragEnter="allowDropOnlyIfInitiatorEqualsComponent(event, availableLangsList);"
dragComplete="availableLangs.refresh();"
/>
I found the solution, which im not sure would work for anyone else. I basically had in my two lists:
`
<mx:List id="srcList" dataProvider="{_source}"
allowMultipleSelection="true"
enabled="{enabled}"
labelField="{labelField}"
iconFunction="iconFunction"
dragEnabled="true"
dropEnabled="true"
dragDrop="doDragDrop(event);"
width="100%"
height="100%"
/>
</mx:VBox>
<mx:VBox paddingTop="50">
<mx:Button label="->" enabled="{enabled}" click="add()"/>
<mx:Button label="<-" enabled="{enabled}" click="rem()"/>
</mx:VBox>
<mx:VBox width="100%" height="100%">
<mx:Label text="{right_col_heading}" />
<mx:List id="dstList" dataProvider="{_destination}"
allowMultipleSelection="true"
enabled="{enabled}"
dragEnabled="true"
dropEnabled="true"
dragDrop="doDragDrop(event);"
width="100%"
height="100%"
labelField="{labelField}"
iconFunction="iconFunction"
verticalAlign="center"
/>`
I basically added a dragMoveEnabled = "true" to both lists and now basically not re-add to the same list an item of itself, but just move the order (which doesnt matter to me as its a soap send and the back-logic would put it in the correct order anyway).
In my case, I used a HashCollection (which extends ArrayCollection) [just google it, you'll find the component]. The dataprovider is bind to this has collection. You would add items to the collection with: dataprovider.put (key, object) instead of dataprovider.addItem(object).
The "hash" will ensure uniqueness in the collection. SO, even if the user drag-drop something that already exists in the hash, the original value would get replaced with the new object (but it wouldn't matter because it's the same value).
The "key", however, must be unique.... otherwise, the hash idea won't work.
Thanks Brice, Those functions were helpful.
For them to work in Spark Lists just update the first function as follows with createDropIndicator instead of showDropFeedback and stop passing the event.
private function onlyAllowMoveDragOverHandlerS(event:DragEvent):void {
event.preventDefault();
event.currentTarget.createDropIndicator();
DragManager.showFeedback(DragManager.MOVE);
}
To illustrate the Q. I'll really over-simplify the example (in reality the code is much more convoluted).
Say you have a flex control, which underneath contains a datagrid. Something like
<mx:DataGrid id="grid" dataProvider="{document.items}">
<mx:columns>
<mx:DataGridColumn headerText="Column 1" dataField="#name"/>
<mx:DataGridColumn headerText="Column 2" dataField="#value"/>
</mx:columns>
</mx:DataGrid>
Where document is a Model object, containing some data. You provide a selection setter on the control, as the clients don't want to know anything about the underlying datamodel :
public function set selectedItem(title:String):Allocation
{
grid.selectedItem = null;
for each(var o:Object in grid.dataProvider)
{
var t:String = o.#title;
if( t == title )
{
grid.selectedItem = o;
return;
}
}
}
So far, so good. Provided document.items is prepopulated, the selection will work correctly.
However. What to do if you already know, at the startup of the application, what the selection ought to be - it's been passed (for example) on the URL? So, in the flex you might have something like
// Initialising now...
mycontrol.document = this.document; // give the control the document
// Fetch titles
new FetchTitlesEvent().dispatch(); // cairngorm-style event
// Set the selection
mycontrol.selectedItem = Application.application.parameters.title;
OOps. Because FetchTitlesEvent operates asynchronously, at the time mycontrol.selectedItem is unable to work. Somehow we need to (re)trigger that code to set the selection on the control. Now, there's several ways I can think to do this, but all have code smell stenches:
1) Do it in the FetchTitlesCommand, after the fetch has been completed
- This pollutes the command with knowledge of the view (or views) that need to know this. Feels like a maintenance nightmare waiting to happen, and means the views are totally bound to commands, and those commands aren't re-usable. Blech.
2) Have a callback from the event when it's complete that does it (either make some composite command that starts in FetchTitlesEvent and ends in a new command to do the set). Seems fragile to me - how does the maintainer know which callbacks are neccesarily required? And it's still binding UI control knowledge into commands. Badness.
3) Have some kind of timer, waiting for the event queue to have been quiescent for a number of seconds. Hackity hackhack.
4) Do it in the control. Bind to the collectionevents in mycontrol on document.items, monitor for changes. Once the row arrives that matches the selection, select it, and stop monitoring for changes.
- Control feels the right place to do it
- collection events sometimes throw exciting CHANGE or REFRESH events though
- seems an expensive monitor to have lying around
I'm pretty much leaning to (4). Are there any other options that flex-ers have used before to trap this issue - particularly are there any codified into libraries that I might use, as it must be a fairly general-purpose problem?
You could set the selectedItem property of the control to a Bindable Object variable:
<mx:Script>
<![CDATA[
[Bindable]
private var mySelectedItem:Object;
]]>
</mx:Script>
<mx:DataGrid id="grid" dataProvider="{document.items}" selectedItem="{mySelectedItem}">
<mx:columns>
<mx:DataGridColumn headerText="Column 1" dataField="#name"/>
<mx:DataGridColumn headerText="Column 2" dataField="#value"/>
</mx:columns>
</mx:DataGrid>
This way can you set or reset the selectedItem at any time, and the control will update the selectedItem based on the Object. For instance, you could set in a Handler for the initialize Event, or creationComplete Event, or in any other function. You may need to call validateNow() on the control when you set the variable, or on creationComplete of the control if you set the variable during initialization.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="initializeHandler();">
<mx:Script>
<![CDATA[
[Bindable]
private var mySelectedItem:Object;
private function initializeHandler():void
{
mySelectedItem = getSelectedItem(); // your logic to determine the initial item to select
}
....
private function grid_creationCompleteHandler():void
{
grid.validateNow();
}
]]>
</mx:Script>
<mx:DataGrid id="grid" dataProvider="{document.items}" selectedItem="{mySelectedItem}" creationComplete="grid_creationCompleteHandler();">
<mx:columns>
<mx:DataGridColumn headerText="Column 1" dataField="#name"/>
<mx:DataGridColumn headerText="Column 2" dataField="#value"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>
The correct way to do this is with the creationComplete event if I am understanding your question correctly. All UI objects in flex broadcast a creationComplete event that lets you know when everything is done and it is ready to go. You can either listen for the creationComplete event on the grid itself or you can listen for it on the application since it will not broadcast until all of it's children (which is everything) is created.
Unfortunately it's not a creationcomplete issue - it's not the initialization of the control that's the problem, it's the initialization of the data. Since it's asynchronous, you don't know when it will eventually arrive.
Try using a ChangeWatcher
var cw:ChangeWatcher = ChangeWather.watch(this, ["document","items"],
function(e:Event):void
{
//check to see if the data we want to select has arrived
//once we've selected the data, we don't need this handler anymore
cw.unwatch();
});
Pardon any syntactical bugs, I don't have FB in front of me.