I have many buttons in Main.mxml. I'm trying to move the button functionality into a Class and have Event Listeners inside the class respond to Click and call other functions. I have written:
Main.mxml
<mx:Button x="23.5" y="10" label="checker" click="{goListen()}" />
<mx:Button id="btnT1" x="252.5" y="10" label="t1" />
<mx:Button id="btnT2" x="309" y="10" label="t2"/>
<mx:Button id="btnT3" x="366" y="10" label="t3"/>
Main.as
private function goListen():void
{
var t:ButtonListener = new ButtonListener(btnT1, btnT2, btnT3);
}
ButtonListener.mxml
package com.util
{
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import mx.controls.Alert;
import mx.controls.Button;
public final class ButtonListener extends EventDispatcher
{
private var __btnArray:Array;
public function ButtonListener(...btnList)
{
__btnArray = new Array();
for each (var item:Button in btnList)
{
__btnArray.push(item);
}
buildListeners();
}
private function buildListeners():void
{
for each (var item:Button in __btnArray)
{
item.addEventListener(MouseEvent.CLICK, traceMe, false, 0, true);
}
}
private function traceMe(event:MouseEvent):void
{
trace(event.target.label + " was clicked");
}
}
}
so when I debug, I see the array filling up with the buttons, but the traceMe() function won't work. Not sure how I can get this to work. Or do I just have to have 30 event listeners and corresponding functions in the main class.
It looks like you have two different options or problems. If you change the last parameter in:
item.addEventListener(MouseEvent.CLICK, traceMe, false, 0, true);
to false, then everything should work because your event listener will stick around to handle the mouse clicks. Of courses, this means that if you click on your "checker" button a second time, you'll then have two sets of listeners responding to mouse clicks of buttons one, two, and three.
So, it's likely that the real solution you're interested in is leaving the line quoted above the same and instead changing the following line:
var t:ButtonListener = new ButtonListener(btnT1, btnT2, btnT3);
If you change the above line to store your button listener as a part of your class it will be available to respond to the mouse clicks, rather than having been garbage collected:
_buttonListener = new ButtonListener(btnT1, btnT2, btnT3);
That, of course, assumes that you have defined _buttonListener within an mx:script block:
<mx:Script><![CDATA[
var _buttonListener:ButtonListener;
]]></mx:Script>
EDIT per comment:
In the code provided, t, the ButtonListener, goes out of scope. When it does, it is garbage collected unless you use strong references, which you do not per the last parameter in your addEventListener call.
Thus, make the button listener a member of the main class:
Main.mxml would then read:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Button x="23.5" y="10" label="checker" click="{goListen()}" />
<mx:Button id="btnT1" x="252.5" y="10" label="t1" />
<mx:Button id="btnT2" x="309" y="10" label="t2"/>
<mx:Button id="btnT3" x="366" y="10" label="t3"/>
<mx:Script>
<![CDATA[
private var _buttonListener:ButtonListener;
private function goListen():void
{
_buttonListener = new ButtonListener(btnT1, btnT2, btnT3);
}
]]>
</mx:Script>
</mx:Application>
Since the event listener will no longer go out of scope, the weak references used by the event listeners will work as expected, being garbage collected when __buttonListener goes out of scope.
Since the click event of Button bubbles, you can just listen for a click event on the main application file and delegate to a handler function in a class.
Or you can call the handler directly on the click of your button.
private var controller:ButtonListener = new ButtonListener();
<mx:Button id="btnT1" x="252.5" y="10" label="t1" click="controller.handleClick(event)"/>
Related
I am currently trying to put together a simple Illustrator plugin, and coming from a design background this is proving to be quite a task, I have experience with JS, but not with Flex.
What I want to do is to have a panel in Illustrator, with an input field and a button. You type something in the input and press the button and a text frame with the desired text is added to the canvas.
But how do I pass the value from a mx:Textinput to the Controller.as file? I couldn't find an answer on the web.
This is my main.mxml file:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" historyManagementEnabled="false">
<mx:Script>
<![CDATA[
private var c:Controller = new Controller();
]]>
</mx:Script>
<mx:VBox height="100%" width="100%" verticalAlign="middle" horizontalAlign="center">
<mx:Label text="myVariable"></mx:Label>
<mx:TextInput name="TextValue"/> // I want the text value to be passed to the Controller class so I can pass it on to my JSX function
<mx:Button label="Run" click="c.run()"/>
</mx:VBox>
</mx:Application>
And this is my Controller.as file:
package
{
import flash.external.HostObject;
public class Controller
{
[ Embed (source="myScript.jsx" , mimeType="application/octet-stream" )]
private static var myScriptClass:Class;
public function run():void {
var jsxInterface:HostObject = HostObject.getRoot(HostObject.extensions[0]);
jsxInterface.eval( new myScriptClass ().toString());
//calling from AS to JSX
jsxInterface.myJSXFunction (myVariable); //This is where I want the value to be passed to
}
}
}
You might also pass the string directly to the c.run() call.
public function run(myString:String):void {
...
jsxInterface.myJSXFunction (myString)
...
and then
<mx:TextInput id="TextValue"/>
<mx:Button label="Run" click="c.run(TextValue.text)"/>
Just another approach.
Loic
First declare public property public var myTextValue : String; in your Controller.
Then declare bidirectional binding in your MXML <mx:TextInput text="#{c.myTextValue}"/>
Now you have myTextValue property always containing the actual value.
But bidirectional binding was introduced not that long time ago.
Alternatively, you can add a change event listener to your TextInput instance <mx:TextInput id="myTextInput" change="c.myTextValue = myTextInput.text"/>
I have prepared a simplified test case for my question. It will run instantly in your Flash Builder if you put the 2 files below into a project.
I'm trying to display a List of strings and a confirmation checkbox in a popup:
In the real application I dispatch a custom event with the string selected in the list, but in the test code below I just call trace(str);
My problem: if I use click event, then the window closes, even if I click at a scrollbar (the !str check below doesn't help, when an item had been selected in previous use). And if I use change event, then the window doesn't close, when I click on the same item as the last time. And the itemClick event seems not to be present in spark.components.List anymore.
Any suggestions please on how to handle this probably frequent problem?
Writing a custom item renderer and having a click event handler for each item seems to be overkill for this case, because I have strings in the list.
Test.mxml: (please click myBtn few times - to see my problems with click and change)
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="400" minHeight="300">
<fx:Script>
<![CDATA[
import mx.managers.PopUpManager;
private var _popup:Popup = new Popup();
private function showPopup(event:MouseEvent):void {
PopUpManager.addPopUp(_popup, this, true);
PopUpManager.centerPopUp(_popup);
}
]]>
</fx:Script>
<s:Button id="myBtn" right="5" bottom="5"
label="Open window" click="showPopup(event)" />
</s:Application>
Popup.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="240" height="240"
creationComplete="init(event)"
close="close()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.events.CloseEvent;
import mx.events.ItemClickEvent;
import mx.managers.PopUpManager;
private var myData:ArrayList = new ArrayList();
private function init(event:FlexEvent):void {
// XXX in the real app data is updated from server
myData.removeAll();
for (var i:uint = 1; i <= 10; i++)
myData.addItem('Item #' + i);
}
public function close(event:TimerEvent=null):void {
PopUpManager.removePopUp(this);
}
private function handleClick(event:MouseEvent):void {
var str:String = myList.selectedItem as String;
if (!str)
return;
if (myBox.selected) {
Alert.show(
'Select ' + str + '?',
null,
mx.controls.Alert.YES | mx.controls.Alert.NO,
null,
handleConfirm,
null,
mx.controls.Alert.NO
);
} else {
sendEvent();
}
}
private function handleConfirm(event:CloseEvent):void {
if (event.detail == mx.controls.Alert.YES)
sendEvent();
}
private function sendEvent():void {
close();
// XXX in the real app dispatchEvent() is called
trace('selected: ' + (myList.selectedItem as String));
}
]]>
</fx:Script>
<s:VGroup paddingLeft="20" paddingTop="20"
paddingRight="20" paddingBottom="20" gap="20"
width="100%" height="100%">
<s:List id="myList" dataProvider="{myData}"
click="handleClick(event)"
width="100%" height="100%" fontSize="24" />
<s:CheckBox id="myBox" label="Confirm" />
</s:VGroup>
</s:TitleWindow>
Also I wonder, why do I get the warning above:
Data binding will not be able to detect assignments to "myData".
The Spark List dispatches an 'IndexChangeEvent.CHANGE'. You can listen for this event to know when the selection in the List has changed.
<s:List id="myList" dataProvider="{myData}"
change="handleIndexChange()"
width="100%" height="100%" fontSize="24" />
That event is only dispatched whenever the selected index actually changes, which means that when you reopen the window a second time an item might still be selected and when you click on that one, no CHANGE event will be fired. To fix this just deselect the selection before you close the window:
public function close():void {
myList.selectedIndex = -1;
PopUpManager.removePopUp(this);
}
Also make sure to dispatch your event with the selected item before you close the window (and deselect it).
As for your question about the binding warning: you get that message because you didn't mark 'myData' to be bindable. To fix this just use the [Bindable] tag:
[Bindable]
private var myData:ArrayList = new ArrayList();
or skip the binding altogether if you don't need it and just assign the dataprovider to the list in ActionScript:
myList.dataProvider = myData;
I'd recommend two solutions if you absolutely wnat to display what item was selected. Otherwise, the solution provided by RIAStar would do the trick.
Listen to rendererAdd and rendererRemove events within your PopUp
As explained here, you can easily access to your list's renderers without interfering with its virtualLayout business.
Use a custom renderer
I know. But as long as you keep your code clean, itemRenderers won't blow up your application's memory. They're made to render huge amount of items without memory leaks.
In your Test.mxml, modify the codes like:
<fx:Script>
<![CDATA[
import mx.managers.PopUpManager;
private var _popup:Popup;
private function showPopup(event:MouseEvent):void {
_popup = new Popup();
PopUpManager.addPopUp(_popup, this, true);
PopUpManager.centerPopUp(_popup);
}
]]>
</fx:Script>
And in your Popup.mxml, I am not sure why you have the TimerEvent in the close function.
Also the trace won't be shown, as you are calling the close() function immediately after the alert's YES button has been clicked..
I have a List with an ItemRenderer. When I click a button, then a property of the ArrayCollection I bound to the list is changed.
When I click the button, then it does change the property, but the list doesn't change.
How do I solve this.
Here's my code
<fx:Script>
<![CDATA[
[Bindable]
public var controllers:ControllerCollection = new ControllerCollection();
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
//these methods don't work unfortunatly
controllers.itemUpdated(controllers[0]);
controllers.refresh();
}
]]>
</fx:Script>
<mx:List id="listControllers" dataProvider="{controllers}">
<mx:itemRenderer>
<fx:Component>
<solutionItems:displaySolutionItem visible="{data.meetsRequirements}" />
</fx:Component>
</mx:itemRenderer>
</mx:List>
<mx:Button label="test" click="hideTheFirstItem(event)" />
(ControllerCollection extends ArrayCollection)
Thanks!
Vincent
Two ways:
collection.refresh()
collection.itemUpdated()
Of course, ControllerCollection is not a standard Flex Collection class; so I am just assuming that it implements the ICollectionView interface.
Update:
I do notice that your code is set to modify the first element of the ArrayCollection
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
//these methods don't work unfortunatly
controllers.itemUpdated(controllers[0]);
controllers.refresh();
}
I wanted to be sure to specify that the first element of the collection may not be the first element currently visible in the view. I wonder if that is causing you issues.
Without seeing your item renderer, I need to make some assumptions.
First, I will assume that your item renderer is using data binding to the meetsRequirements property. If that is the case, then the meetsRequirements property needs to notify when that property changes. If you add the [Bindable] tag to that property or the Controller class, then the meetsRequirements property will notify the itemRenderer to update that field based on your data binding.
If my assumptions are wrong, we need to see the code to give you any further thoughts.
First, don't try to create new collections if you don't need to.
I believe your problem lies with this statement: (controllers[0] as Controller).meetsRequirements = false; which should fail on compile because a collection item cannot be retrieved using the square bracket annotation. You need to use getItemAt(index:int) function.
Furthermore, you wouldn't want to set visible to false to an item renderer if you want to 'remove' it because then you'd have an empty spot. What you want to do is filter it out:
<s:Application creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] public var data:ArrayCollection = new ArrayCollection();
private function onCreationComplete():void
{
// TODO: need to add data here
// Adding filter function
data.filterFunction = filterItems;
}
private function filterItems(item:Object):Boolean
{
return item.meetsRequirements;
}
private function hideFirstItem():void
{
if(data.length > 0)
{
Controller(data.getItemAt(0)).meetsRequirements = false;
}
data.refresh();
}
]]>
</fx:Script>
<mx:List id="listControllers" dataProvider="{data}" />
<mx:Button label="test" click="hideFirstItem()" />
</s:Application>
This should do it. Untested though.
Try this:
<fx:Script>
<![CDATA[
[Bindable(Event="refreshMyList")]
public var controllers:ControllerCollection = new ControllerCollection();
private function hideTheFirstItem(evt:MouseEvent):void
{
(controllers[0] as Controller).meetsRequirements = false;
dispatchEvent(new Event("refreshMyList"));
}
]]>
</fx:Script>
Suppose I have an application and a global event listener in it. Should the key events, who are fired in the PopUp, be caught by that listener? Or maybe popups are not placed in that hierarchy?
Here's simplified test-code for you to understand what I'm talking about:
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()">
<mx:Script><![CDATA[
private function init():void {
FlexGlobals.topLevelApplication.addEventListener(KeyboardEvent.KEY_DOWN, myKeyDownHandler);
}
private function myKeyDownHandler(event:KeyboardEvent):void {
Alert.show("It works!");
}
private function makeNewPopup():void {
var win:PopupWindow = new PopupWindow(this, new TestingForm(), true, "Popup", false);
win.showPopup();
}
]]></mx:Script>
<mx:VBox>
<mx:TextInput/>
<mx:Button label="MakePopup" click="makeNewPopup()"/>
</mx:VBox>
</mx:Canvas>
Ok, what we have .. after running the application, put the input focus into the TextInput and press any letter. The Alert will be fired. Now, press the MakePopup and do the same in it TextInput .. no any feedback from it.
Any thoughts about that?
The parent of all popups is SystemManager. So, use FlexGlobals.topLevelApplication.systemManageror stage.
In certain cases, I can't seem to get components to receive events.
[edit]
To clarify, the example code is just for demonstration sake, what I was really asking was if there was a central location that a listener could be added, to which one can reliably dispatch events to and from arbitrary objects.
I ended up using parentApplication to dispatch and receive the event I needed to handle.
[/edit]
If two components have differing parents, or as in the example below, one is a popup, it would seem the event never reaches the listener (See the method "popUp" for the dispatch that doesn't work):
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
initialize="init()">
<mx:Script>
<![CDATA[
import mx.controls.Menu;
import mx.managers.PopUpManager;
// Add listeners
public function init():void
{
this.addEventListener("child", handleChild);
this.addEventListener("stepchild", handleStepchild);
}
// Handle the no pop button event
public function noPop(event:Event):void
{
dispatchEvent(new Event("child"));
}
// Handle the pop up
public function popUp(event:Event):void
{
var menu:Menu = new Menu();
var btnMenu:Button = new Button();
btnMenu.label = "stepchild";
menu.addChild(btnMenu);
PopUpManager.addPopUp(menu, this);
// neither of these work...
this.callLater(btnMenu.dispatchEvent, [new Event("stepchild", true)]);
btnMenu.dispatchEvent(new Event("stepchild", true));
}
// Event handlers
public function handleChild(event:Event):void
{
trace("I handled child");
}
public function handleStepchild(event:Event):void {
trace("I handled stepchild");
}
]]>
</mx:Script>
<mx:VBox>
<mx:Button label="NoPop" id="btnNoPop" click="noPop(event)"/>
<mx:Button label="PopUp" id="btnPop" click="popUp(event)"/>
</mx:VBox>
</mx:Application>
I can think of work-arounds, but it seems like there ought to be some central event bus...
Am I missing something?
Above is correct.
You are dispatching the event from btnMenu, but you are not listening for events on btnMenu - you are listening for events on the Application.
Either dispatch from Application:
dispatchEvent(new Event("stepchild", true));
or listen on the btnMenu
btnMenu.addEventListener("stepchild",handleStepChild);
btnMenu.dispatchEvent(new Event("stepchild",true));
You are attaching the listener to this when the event is getting dispatched from btnMenu.
This should work:
dispatchEvent(new Event("stepchild", true));
ps. There is really no reason to put an unnecessary 'this' everywhere, unless it's explicitly required to overcome scope issues. In this case you can just leave every this out.