Access to undefined property itemRenderer (Keyboard Event) - actionscript-3

I am trying to get the following code to run when I hit enter on a row within a data grid, it works when I click on the row (ListEvent) but how would I enable this to work when the enter key is hit (KeyboardEvent). I have the enter key working although it throws this error every time
private function onButtonClick(evt:KeyboardEvent):void
{
var item:Object = evt.itemRenderer.data;
openWorkflowItem(item.date.date, item.workFlowID);
$multiEdit = false;
if (target === currentWorkflowItems)
{
$histFilter['sym'] = item.sym;
histSym.text = item.sym;
applyHistFilters();
}
}
<mx:AdvancedDataGrid
id="historicalWorkflowItems"
dataProvider="{$historicalWFItems}"
width="100%" height="100%"
itemClick="{onWFItemClick(event)}"
keyDown="if (event.keyCode==Keyboard.ENTER){ onButtonClick(event)}"
borderStyle="none"
sortExpertMode="true"
useHandCursor="true"
headerShift="{saveColumnSettings('historical', historicalWorkflowItems)}"
columnStretch="{saveColumnSettings('historical', historicalWorkflowItems)}"
horizontalScrollPolicy="auto"
verticalScrollPolicy="auto"
allowMultipleSelection="true"
>

The problem is that you are assigning a key listener to the grid, so there's no specific item in this context. The keyDown event fires whenever a key is pressed with focus anywhere within the grid. You want something like itemKeyDown, but the grid does not seem to provide you with such an event.
This is why I asked you to check Event/target to see what value it holds. If an item renderer has focus and the user presses a key, your handler will fire and you can check if the event target is an item renderer, ex if(event.target is AdvancedDataGridItemRenderer). (The event currentTarget will always be the grid.) Now, I'm not super familiar with the implementation of AdvancedDataGrid item rendering, but it's likely that the event target will not itself be an item renderer, only a child of an item renderer (say a text field inside the item renderer). To check if the target is inside an item renderer is possible by walking the display tree and checking if a parent is an item renderer class:
function findItemRenderer(object:DisplayObject):AdvancedDataGridItemRenderer {
var current:DisplayObject = object;
while(current && !(current is AdvancedDataGrid)){
if(current is AdvancedDataGridItemRenderer)
return current as AdvancedDataGridItemRenderer;
current = current.parent;
}
return null;
}
Now you can handle your event like this:
function gridKeyDown(e:KeyboardEvent):void {
var item:AdvancedDataGridItemRenderer = findItemRenderer(e.target as DisplayObject);
if(item){
// do stuff with your item
}
}
But this is getting kind of silly to search the display hierarchy like this. What you should do is just dispatch an event from inside your item renderer, so that the event target is always the item renderer itself. For example:
class MyItemRenderer extends AdvancedDataGridItemRenderer {
public static const ITEM_ENTER:String = "itemEnter";
public MyItemRenderer(){
addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
}
private function keyDown(e:KeyboardEvent):void {
if(e.keyCode == Keyboard.ENTER)
dispatchEvent(new Event(ITEM_ENTER, true)); // bubbles=true
}
}
Now you can handle this event:
myGrid.addEventListener(MyItemRenderer.ITEM_ENTER, itemEnter);
function itemEnter(e:Event):void {
var item:MyItemRenderer = e.target as MyItemRenderer;
// do stuff with your item
}

Related

AS3 - How do you call previous currentTarget from within a different event?

I have a dropdown menu that lets you select an item to be placed on the stage. The item is drag and droppable so I use event.currentTarget.startDrag(); to start the drag. Ok, everything works fine so far.
However, I also need to be able to rotate the item while it is being dragged (using the spacebar).
stage.addEventListener(KeyboardEvent.KEY_DOWN, myKeyDown);
function myKeyDown(e:KeyboardEvent):void{
if (e.keyCode == Keyboard.SPACE){
rotate++;
if (rotate == 5){
rotate = 1;
}
WHATGOESHERE?.gotoAndStop(rotate);
}
If I hardcode in an instance name of an object everything works fine - so the rotate function is working properly. The problem is, how can I reference event.currentTarget from the startDrag function while inside of the keyDown event?
My first thought was to set event.currentTarget to a variable and then calling the variable from within the keyDown event. However, targetHold = event.currentTarget; does not seem to record the instance name of the object being clicked...
public var targetHold:Object = new Object;
function ClickToDrag(event:MouseEvent):void {
event.currentTarget.startDrag();
targetHold = event.currentTarget;
trace ("targetHold " + targetHold);
stage.addEventListener(KeyboardEvent.KEY_DOWN, myKeyDown);
function myKeyDown(e:KeyboardEvent):void{
if (e.keyCode == Keyboard.SPACE){
rotate++;
if (rotate == 5){
rotate = 1;
}
targetHold.gotoAndStop(rotate); //does not work
}
}
function ReleaseToDrop(event:MouseEvent):void {
event.currentTarget.stopDrag();
}
As you click the object, it should have focus. If you register the listener for the KeyboardEvent on the object and not on the stage, it will be .currentTarget.
Here's an example of what I have in mind. Right after starting the drag, add the listener to the same object instead of the stage.
event.currentTarget.startDrag();
event.currentTarget.addEventListener(KeyboardEvent.KEY_DOWN, myKeyDown);
The proper way of doing this would be to define all the functionality in a class. Within a self contained class, you would not need any .currentTarget.
Here is how I would do this: (well, actually I'd follow #null's advice and encapsulate it in a sub class that all your dragable objects would extend, but that is a little broad so this will do)
public var targetHold:MovieClip; //don't make a new object, just create the empty var
public function YourConstructor(){
//your other constructor code
stage.addEventListener(KeyboardEvent.KEY_DOWN, myKeyDown); //don't add the listener in the click function
}
private function clickToDrag(event:MouseEvent):void {
if(targetHold) ReleaseToDrop(null); //safeguard in case flash lost the focus during the mouse up
targetHold = event.currentTarget as MovieClip; //assign the current target. Might as well cast it as MovieClip and get code completion benefits
targetHold.startDrag();
trace ("targetHold " + targetHold);
}
private function myKeyDown(e:KeyboardEvent):void{
//check if target hold exists
if (targetHold != null && e.keyCode == Keyboard.SPACE){
rotate++;
if (rotate == 5){
rotate = 1;
}
targetHold.gotoAndStop(rotate);
}
}
private function ReleaseToDrop(event:MouseEvent):void {
if(targetHold) targetHold.stopDrag();
targetHold = null;
}

Flex topLevelApplication and SuperTabNavigator mouse event handler

I am using the flex SuperTabNavigator and want on closing the tab check if the control button was pressed. I tried:
public static var CONTROL_PRESSED:Boolean = false;
public function init():void {
var clickListener:Function = function _clickListener(event:MouseEvent):void{
trace(event.ctrlKey);
if(event.ctrlKey){
CONTROL_PRESSED = true;
}else{
CONTROL_PRESSED = false;
}
};
FlexGlobals.topLevelApplication.addEventListener(MouseEvent.CLICK, clickListener);
}
The problem with this is that the mouse click is called everywhere in the application except on the tab. I also tried the same code but addEventListener(MouseEvent.CLICK, clickListener); to add the listener to the SuperTabNavigator and it did not work at all. Is there another way to catch the mouse click?
This is because the SuperTabNavigator has a private mouse click handler function that hides the MouseEvent:
private function closeClickHandler(event:MouseEvent):void {
if(this.enabled) {
dispatchEvent(new Event(CLOSE_TAB_EVENT));
}
event.stopImmediatePropagation();
event.stopPropagation();
}
You'll need to modify the SuperTab class in the SuperTabNavigator source to dispatch some CustomEvent with the data you want instead of a plain new Event(CLOSE_TAB_EVENT).
Then change the onCloseTabClicked function in SuperTabBar to know about your CustomEvent. Then you can pass that to your application code (by adding it to the SuperTabEvent, perhaps).

How to handle event for nonvisual objects in Flex

I am trying to perform two way binding e.g I have a button (out of many controls), on its selection, I am showing the values of its diff properties(like height, width etc) in some textinput. This one way process works fine.
But the reverse process doesn't work. i.e When I select some button, and try to change its dimension by entering some value in height, width textinputs, the dimension are not changed.
How to know which button was selected by me? How events needs to be handled here ?
private void Form1_Load(object sender, System.EventArgs e)
{
//Create some data and bind it to the grid
dt1 = GetData(1000, 3);
this.UltraGrid1.DataSource = dt1;
//Set the grid's CreationFilter to a new instance of the NumbersInRowSelectors class.
this.UltraGrid1.CreationFilter = new NumbersInRowSelectors();
}
private void UltraGrid1_InitializeLayout(object sender, Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs e)
{
//Hide the default images that are drawn in the RowSelectors, like the pencil and asterisk, etc.
e.Layout.Override.RowSelectorAppearance.ImageAlpha = Infragistics.Win.Alpha.Transparent;
//Center the text in the RowSelectors.
e.Layout.Override.RowSelectorAppearance.TextHAlign = Infragistics.Win.HAlign.Center;
e.Layout.Override.RowSelectorAppearance.TextVAlign = Infragistics.Win.VAlign.Middle;
//There is no wy to change the width of the RowSelectors.
//Use a smaller font, so that 3-digit numbers will fit.
e.Layout.Override.RowSelectorAppearance.FontData.Name = "Small Fonts";
e.Layout.Override.RowSelectorAppearance.FontData.SizeInPoints = 6;
}
//The NumbersInRowSelectors class. This class Implements a CreationFilter and
//adds a TextUIElement to each RowSelector which displays the row number of
//the row.
public class NumbersInRowSelectors:Infragistics.Win.IUIElementCreationFilter
{
#region Implementation of IUIElementCreationFilter
public void AfterCreateChildElements(Infragistics.Win.UIElement parent)
{
//Don't need to do anything here
}
public bool BeforeCreateChildElements(Infragistics.Win.UIElement parent)
{
//Declare some variables
Infragistics.Win.TextUIElement objTextUIElement;
Infragistics.Win.UltraWinGrid.RowSelectorUIElement objRowSelectorUIElement;
Infragistics.Win.UltraWinGrid.UltraGridRow objRow;
int RowNumber;
//Check to see if the parent is a RowSelectorUIElement. If not,
//we don't need to do anything
if (parent is Infragistics.Win.UltraWinGrid.RowSelectorUIElement)
{
//Get the Row from the RowSelectorsUIElement
objRowSelectorUIElement = (Infragistics.Win.UltraWinGrid.RowSelectorUIElement)parent;
objRow = (Infragistics.Win.UltraWinGrid.UltraGridRow)objRowSelectorUIElement.GetContext(typeof(Infragistics.Win.UltraWinGrid.UltraGridRow));
//Get the Index of the Row, so we can use it as a row number.
RowNumber = objRow.Index;
//Check to see if the TextUIElement is already created. Since
//The RowSelectorsUIElement never has children by default, we
//can just check the count.
if (parent.ChildElements.Count == 0)
{
//Create a new TextUIElement and parent it to the RowSelectorUIElement
objTextUIElement = new Infragistics.Win.TextUIElement(parent, RowNumber.ToString());
parent.ChildElements.Add(objTextUIElement);
}
else
{
//There's already a TextUIElement here, so just set the Text
objTextUIElement = (Infragistics.Win.TextUIElement)parent.ChildElements[0];
objTextUIElement.Text = RowNumber.ToString();
}
//Position the TextUIElement into the RowSelectorUIElement
objTextUIElement.Rect = parent.RectInsideBorders;
//Return True let the grid know we handled this event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return true;
}
//Return false to let the grid know we did not handle the event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return false;
}
#endregion
}
}
Create a "currently selected item" member in the class where the button and the text edit are declared.
In the button selection event listener assign the event target to this member. Then use it in the text edit event listener.
For example:
// It's a declaration of the member variable
private var m_current_btn:Button = null;
// It's an event listener for your button
private function on_selection_change(event:Event):void
{
m_current_btn = event.target as Button;
// button_x and button_y are two text edits
button_x.text = m_current_button.x.toString();
button_y.text = m_current_button.y.toString();
}
// Event listener to track changes in the coordinate text inputs
private function on_coordinate_textedit_change(event:Event):void
{
if (m_current_btn != null)
{
m_current_btn.x = parseInt(button_x.text);
m_current_btn.y = parseInt(button_y.text);
}
}

Actionscript3: add and remove EventListeners (with dynamic name and dynamic variables)

I am making a boardgame in flash Action Script 3. Each position on the board is a buttons like this: button_1_1, button_1_2 etc. Whenever a character is selected you want to move it so the script has to add event listeners for positions around the selected unit
// This function adds or deletes an event listener
function listentoButton (isTrue:int, position_x:int, position_y:int):void {
var myFunction:Function = new Function;
myFunction = function ():void {userClickedPosition(position_x, position_y)};
if (isTrue == 1) {
this["button_position_"+(position_x)+"_"+(position_y)].addEventListener(MouseEvent.CLICK, myFunction);
} else {
this["button_position_"+(position_x)+"_"+(position_y)].removeEventListener(MouseEvent.CLICK, myFunction);
}
}
In the rest of the code I have:
function userClickedPosition(position_x:int, position_y:int)
it selects or deselect a unit
function selectUnit(position_x:int, position_y:int):
it uses the listentoButton(1) function to add 8 listeners (the positions around the clicked unit)
function deselectUnit(position_x:int, position_y:int):
it uses the listentoButton(0) function to delete 8 listeners (the positions around the clicked unit)
My question: adding eventlisteners is no problem but removing them dont seem to work? What did I do wrong?
When you go to remove the event, you are using a new instance of myFunction, not the same one you added it with. You either need to declare the function like you would any other function, and use the event args to examine the button's position like. I Think you want the stageX and stageY properties:
http://www.adobe.com/livedocs/flex/3/langref/flash/events/MouseEvent.html
// This function adds or deletes an event listener
function listentoButton (isTrue:int, position_x:int, position_y:int):void {
if (isTrue == 1) {
this["button_position_"+(position_x)+"_"+(position_y)].addEventListener(MouseEvent.CLICK, myFunction);
} else {
this["button_position_"+(position_x)+"_"+(position_y)].removeEventListener(MouseEvent.CLICK, myFunction);
}
}
function myFunction(eventArg:MouseEvent):void {
//use MouseEvent
};
Or you can create a little MyFunctionParameters class to hold the coordinate information and create a new instance of that class, add it to a collection indexed by the x and y coordinates, and later when you go to remove the event, you would lookup the MySpaceParameters instance in the collection, based on x and y coordinates, then use that to remove function.
class MyFunctionParameters
{
public x:int;
public y:int;
function myFunction(eventArg:MouseEvent):void {
userClickedPosition(x,y);
};
}

Dynamically Handling Events with Function Expression

I have a class which exposes literally dozens of events(before you get of on a tangent about whether that's good/bad design, just know that I didn't make that class). The event object of each event(eventParam in the below code) always has a toDebugString function, that basically creates a string containing all of the event object's property values:
propertyName1: propertyValue1
propertyName2: propertyValue2
propertyName3: propertyValue3
It works so far as creating all of the panels, with the title of each panel being the name of the event. However, the big problem is that all of events end up in the TextArea of the last panel. So there is something I don't understand about the anonymous method. It's as if each iteration of the loop uses the same function, and on the last iteration of the loop it decides that the debugPanel that was just created will be the one that all instances of that function will reference. In other words, a new unique debugSubPanel and TextArea is created in each iteration of the loop, but there is only one debugResponseListener event handler shared by all iterations of the loop. So my question is, how can I dynamically create the event handler function dynamically so that it stays associated with the debugSubPanel that I want it to?
public function debugPanelCreated(event:FlexEvent)
{
//iterate through all of the events exposed by mClient.ResponsesDispatcher
//where key is the name of the event
for (var key:String in mClient.ResponsesDispatcher.respMap)
{
//for each event, create a panel containing a text box
var debugSubPanel:Panel = new Panel();
debugSubPanel.title = debugSubPanel.label = key;
var debugSubPanelTextArea:TextArea = new TextArea();
debugSubPanel.addChild(debugSubPanelTextArea);
var debugResponseListener:Function =
function (eventParam :Object) : void
{
//use debugString function to write the properties
//of eventParam to the text box
debugSubPanelTextArea.text = eventParam .toDebugString();
};
//listen to this event:
mClient.ResponsesDispatcher.addEventListener(key,debugResponseListener);
//add the panel for this event
debugPanel.addChild(debugSubPanel);
}
}
Actionscript includes a feature called closures, which means that when you create an inner function and call it, the variables of its parent function are still available. (This is how debugResponseListener = function() ... works at all.) The issue is that a closure is only created when that function is called, and it uses the variable values from their last setting.
You can get around this by making a function that returns the listener function you want.
function makePanelListener(debugSubPanelTextArea:TextArea) : Function
{
return function(eventParam :Object) : void {
//use debugString function to write the properties
//of eventParam to the text box
debugSubPanelTextArea.text = eventParam .toDebugString();
}
}
and in your original code:
var debugResponseListener:Function = makePanelListener(debugSubPanelTextArea);
(There's a little explanation of what's going on in Explaining JavaScript scope and closures, look for the section called "The Infamous Loop Problem". More on closures at jibbering.)
This is the hack I came up with. I really don't like it, but it'll work for now. Open to suggestions still.
public class ResponseDispatcherToDebugStringHelper
{
public var textArea:TextArea;
public function responseToDebugStringHandler(eventParam:Object) : void
{
//use debugString function to write the properties
//of eventParam to the text box
textArea.text = eventParam.toDebugString();
}
}
public function debugPanelCreated(event:FlexEvent)
{
//iterate through all of the events exposed by mClient.ResponsesDispatcher
//where key is the name of the event
for (var key:String in mClient.ResponsesDispatcher.respMap)
{
//for each event, create a panel containing a text box
var debugSubPanel:Panel = new Panel();
debugSubPanel.title = debugSubPanel.label = key;
var debugSubPanelTextArea:TextArea = new TextArea();
debugSubPanel.addChild(debugSubPanelTextArea);
var helper:ResponseDispatcherToDebugStringHelper =
new ResponseDispatcherToDebugStringHelper();
helper.textArea = debugSubPanelTextArea;
//listen to this event:
mClient.ResponsesDispatcher.addEventListener(key,helper.responseToDebugStringHandler);
//add the panel for this event
debugPanel.addChild(debugSubPanel);
}
}