Adobe/Apache Flex: Modify View in an ActionScript class - actionscript-3

I have a WindowedApplication in Apache/Adobe Flex 4 which currently consists of one view (the view defined in the WindowedApplication MXML).
In that application I have an object which listens to data coming from a network. When data is available a method is called on that object and it shall update my view by changing the text of a label.
I do not have a reference to the view in the network listener object though. How can I get it?
This is part of my MXML where I define my view.
<fx:Script source="./ViewCodeBehind.as"/>
<!-- ommited stuff -->
<s:Label id="errorLabel"
text=""
fontSize="14"/>
<!-- Stuff in between -->
<s:Button label="Get Status"
click="getStatus();"/>
The code which is called when the button is clicked:
public function getStatus(): void
{
var networkGateway: NetworkGateway = new NetworkGatewayImpl();
networkGateway.getConnectionStatus();
}
And the NetworkGatewayImpl
public class NetworkGatewayImpl implements NetworkGateway
{
public function NetworkGatewayImpl()
{
}
public function getConnectionStatus(): void
{
// Start asynchronous network call
// when error occurs onNetworkError() is called
}
private function onNetworkError(): void
{
// Set "errorLabel" here: How?
}
}
Essentially I want to know some ways to update "errorLabel" from the NetworkGatewayImpl.

Based on your code, there could be multiple ways to solve this. Easiest way (as per me) would be to dispatch an event from the NetworkGatewayImpl class and listen to it on the instance you have created in the view class. So sample code would look like this:
public function getStatus(): void
{
var networkGateway: NetworkGateway = new NetworkGatewayImpl();
networkGateway.addEventListener("networkError", onNetworkError);
networkGateway.getConnectionStatus();
}
private function onNetworkError(e:Event):void
{
networkGateway.removeEventListener("networkError", onNetworkError);
this.errorLabel.text = "Your Text Here";
}
Dispatch your event like this from your NetworkGatewayImpl class:
private function onNetworkError(): void
{
this.dispatchEvent("networkError");
}
You will have to ensure that your NetworkGatewayImpl also implements the IEventDispatcher interface to be able to dispatch events.
Also, best practice would be to create a custom Event class (extending the Event class) and use constants instead of the literal 'networkError'
Hope this helps.

Related

AS3 MVC Design Multiple Controllers

So I'm reading a book about MVC and the author create a controller (PlayerController) and put some functions for keyboard use and some for mouse use. But he just comment out every keyboard use.
It gave me a idea to create 2 controllers, PlayerMouseController and PlayerKeyboardController so I can decide how to control the player changing one line. And if I can design this way, later I can add a AIController for monsters that use the same view and model but are controlled by AI and so on...
I have my model Player and it do the physics stuff. Now I want two controller, one for mouse and other for keyboard. So I create a PlayerMouseController and PlayerKeyboardController.
The PlayerMouseController has 2 functions: processUpdate() and processMouseDown()
The PlayerKeyboardController has 2 functions: processKeyDown() and processKeyUp()
I create the object like this:
_player = new Player();
_playerController = new PlayerMouseController(_player);
_playerView = new PlayerView(_player, _playerController, stage);
addChild(_playerView);
If I want to change the controller I can just change the _playerController line for this:
_playerController = new PlayerKeyboardController(_player);
And it works fine... But I dont know if the design I use is fine for a large project
To make this work I have to create a Controller class with nothing so I can extends the others controllers and my view can call all methods.
public class Controller
{
public function processKeyDown(e:KeyboardEvent):void
{
}
public function processKeyUp(e:KeyboardEvent):void
{
}
public function processUpdate(stage:Stage):void
{
}
public function processMouseDown(e:MouseEvent):void
{
}
}
In my view (PlayerView) I accept any Controller:
public function PlayerView(model:Player, controller:Controller, stage:Stage)
{
_model = model;
_controller = controller;
_stage = stage;
}
and I decide what to use based on its type:
if (_controller is PlayerKeyboardController)
{
_stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
_stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
else if (_controller is PlayerMouseController)
{
_model.addEventListener(Model.UPDATE, onUpdate);
_stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
// EVENTS PlayerKeyboardController
private function onKeyDown(e:KeyboardEvent):void
{
_controller.processKeyDown(e);
}
private function onKeyUp(e:KeyboardEvent):void
{
_controller.processKeyUp(e);
}
// EVENTS PlayerMouseController
private function onUpdate(e:Event):void
{
_controller.processUpdate(_stage);
}
private function onMouseDown(e:MouseEvent):void
{
_controller.processMouseDown(e);
}
So... this is a good idea? How should I improve??
I think as your project evolves you'll soon hit a bottleneck with this kind of approach. I'd suggest creating a general IEntityController interface (don't be afraid of the word 'entity', it just shows that this is a game object controller, something that is parent for example of hero, enemy, etc.).
Then I'd create two separate implementations of this interface: BasicMouseController and BasicKeyboardController, so that I'd have two branches of these controllers with basic logic. If you need additional functionality for the Hero, you'd create a HeroMouseController class which would extend the BasicMouseController class and would have the advantage of calling super methods and adding the extended functionality easily.
You'd still have the benefit of passing different controllers to the PlayerView class as it's constructor would receive an IEntityController as a parameter, meaning anything implementing this class can be passed in.
There are many approaches for problems like this and StackOverflow is usually not meant to give these kind of answers, as every answer to these kind of questions is subjective and this website more fit for Problem/Solution kind of posts.

Trigger an Event when value has changed

I'm trying to trigger an event (function) in an air application when a value has changed (i.e. when a user has logged in), but so far I've no succeeded in triggering my function...
I have following code:
A user class:
[Bindable]
public class User
{
private var m_rssLink:String;
public function get rssLink():String{
return m_rssLink;
}
public function set rssLink(value:String):void{
m_rssLink = value;
}
}
A header component with id header where the user logs in and the user is defined:
<fx:Declarations>
<user:User id="user" />
</fx:Declarations>
And finally the mxml code with the actionscript:
private var loginWatcher:ChangeWatcher;
protected function creationCompleteHandler(event:FlexEvent):void
{
this.loginWatcher = ChangeWatcher.watch(header.user, ["rssLink"], sendRequest);
}
I've tried to trigger the sendRequest function in a lot of ways by now (with [bindable (event="")], with a bindsetter,... ) and so far Google brought me no other options.
Anyone sees where my code fails, or knows a better way to accomplish this?
Well, you have already made a setter property for User. Use that to track user states.
public function set rssLink(value:String):void{
if (m_rsslink==value) return;
m_rssLink = value;
dispatchEvent(...); // use correct event(s) for certain values, then catch
// them in your watcher class. Use FlexEvents if you want.
}

Custom Event from Item Renderer not detected by a class

I am trying to dispatch a custom event from an item renderer(which is a child of the Main application file / root).
Code in Main.mxml:
<s:List id="movieGrid"itemRenderer="views.MovieRenderer" dataProvider="{new ArrayCollection()}">
</s:List>
<s:Group width="100%" height="100%" bottom="60">
<views:DetailedViewInfo id="detailed" includeIn="MoviePage" />
</s:Group>
Renderer (something clicked):
MyEventDispatcher.Dispatcher.dispatchEvent(new MovieClickEvent(MovieClickEvent.CLICKED, data));
DetailedViewInfo (creation complete):
MyEventDispatcher.Dispatcher.addEventListener(MovieClickEvent.CLICKED, clickHandler);
MyEventDispatcher:
package events
{
import flash.events.EventDispatcher;
public class MyEventDispatcher
{
public static var Dispatcher:EventDispatcher = new EventDispatcher();
}
}
Event:
package events
{
import flash.events.Event;
public class MovieClickEvent extends Event
{
public function MovieClickEvent(type:String, theMovieData:Object, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this._result = theMovieData;
}
public function get result():Object
{
return this._result;
}
override public function clone():Event
{
return new MovieClickEvent(type, result, bubbles, cancelable)
}
public static const CLICKED:String = "MovieClickEvent.CLICKED";
private var _result:Object;
}
}
I am able to listen for the event successfully in the Main.mxml but I also need to detect it in a SkinnableContainer - "DetailedViewInfo" that is also a child of Main.mxml:
It his possible at all? I tried importing all related events / classes and same for declarations. It does not work even if I comment out the event listener in Main.mxml. I tried adding a declaration to the item renderer in DetailedViewInfo but that crashes the application with no understandable error.
Could someone explain to me how this should be done? I am using custom events all over the place in my application and hadn't had this happen before. Any help highly appreciated!
It would seem you're adding the event listener after the event was dispatched. I see you have an includeIn statement there: this means the DetailedViewInfo component will not be immediately created, but only when the MoviePage state is entered. The event may be dispatched before the component is created and the event listener attached.
The quick fix for this issue, is to not use includeIn, but set the component's visibility according to the current state:
<views:DetailedViewInfo id="detailed" visible="false" includeInLayout="false"
visible.MoviePage="true" includeInLayout.MoviePage="true" />
However, you may want to review your architecture if you need to resort to this. Unfortunately I can't tell you much more than that, since I don't know your current architecture.

[Event(name=“”)]: How is it used and how do custom events work?

I have to work on a project someone else started, but can't contact him because he's out of the country at the moment. Anyway. There is a main mxml and a custom component called "admin".
In admin he declared an event like this:
<fx:Metadata>
[Event(name="sluitFrame")]
</fx:Metadata>
And in a certain function within the component, this event is called upon like this:
dispatchEvent(new Event("sluitFrame"));
This event sluitFrame closes a frame with some admin tools. I need to change the way this works so i'd like to find the corresponding code. On the main mxml there's this code:
<comp:Admin id="compAdmin" creationPolicy="none"
sluitFrame="verbergAdminComponent(event)"/>
So if i understand correctly sluitFrame calls to a custom even called "verbergAdminComponent(event)". So i guess i need this event to change the way the admin frame is closed etc. But this event is nowhere to be found. So I don't understand how "verbergAdminComponent(event)" works or where I can make changes to this event.
Any help is more than welcome and very much needed :)
The [Event... line simply lets the compiler and IDE know that the component/class (in this case, Admin) may dispatch an event by that name. This is important because when someone declares an instance of Admin in a MXML tag, the compiler knows that this event (in this case, sluitFrame) is a valid property. In other words, it lets the compiler know that you can set an event listener in the MXML tag. In your case, every time the Admin object dispatches a sluitFrame event, the function verbergAdminComponent is called and the sluitFrame Event is passed to it.
verbergAdminComponent would be the name of an event handler. It should be a method either in the mxml, in an included .as, or in a base class of that MXML.
Types in Events
If you use an Event in Flash, you could dispatch any named type, since it is a string. So it does not matter how you call it, as long as the listener listens to the exact same type. That's why it works. There is no magic evolved. However, I would choose to use an custom event in that case.
How custom events work
Take a look at custom events work, so you also understand where meta data comes in.
package com.website.events
{
import flash.events.Event;
/**
* #author ExampleUser
*/
public class MyCustomEvent extends Event
{
/**
*
*/
public static const CLOSE_WINDOW:String = "MyCustomEvent.closeWindow";
public function MyCustomEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false):void
{
super(type, bubbles, cancelable);
}
override public function clone():Event
{
return new MyCustomEvent(this.type, this.bubbles, this.cancelable);
}
override public function toString():String
{
return formatToString("MyCustomEvent", "type", "bubbles", "cancelable", "eventPhase");
}
}
}
I use a static constant instead of directly a string (like your example), so it is typed more strictly.
In actionscript, you'd dispatch the custom event like this. Let's say you create a class named Window.
package com.website.ui
{
/**
* #eventType com.website.events.MyCustomEvent.closeWindow
*/
[Event(name="MyCustomEvent.closeWindow", type="com.website.events.MyCustomEvent")]
import flash.display.Sprite;
import flash.events.MouseEvent;
import com.website.events.MyCustomEvent;
/**
* #author ExampleUser
*/
public class Window extends Sprite
{
public var mcCloseButton:Sprite;
public function Window():void
{
this.mcCloseButton.addEventListener(MouseEvent.CLICK, handleCloseButtonClick);
}
private function handleCloseButtonClick(event:MouseEvent):void
{
this.dispatchEvent(new MyCustomEvent(MyCustomEvent.CLOSE_WINDOW));
}
}
}
This is the class where the meta data should be located. This class is dispatching the event. Other classes that dispatches the same event, could have the meta-data too.
So, Window is dispatching an event with type CLOSE_WINDOW when user clicked on the close button. In another file you would listen to it and do something with it.
package com.website
{
import flash.display.Sprite;
import com.website.events.MyCustomEvent;
import com.website.ui.Window;
/**
* #author ExampleUser
*/
public class Main extends Sprite
{
private var _window:Window;
public function Main():void
{
this._window:Window = new Window();
// a smart code-editor would give you a hint about the possible events when you typed "addEventListener"
this._window.addEventListener(MyCustomEvent.CLOSE_WINDOW, handleWindowClosed);
this.addChild(this._window);
}
private function handleWindowClosed(event:MyCustomEvent):void
{
// do something
this._window.visible = false;
}
}
}
This should work.
Ofcourse in a real-world situation MyCustomEvent would be named WindowEvent.
Event Meta Data
The meta data could be used to give hints to the compiler and nowadays smart code editors (FDT, FlashDevelop, FlashBuilder, IntelliJ etc.) can give code completion. It's basically a description of what kind of events may be dispatched by a class, so you know what listeners could be used.
The code should work even when the meta data is deleted.
The Event meta has a name and a type. The name should be the exact value of the type. In the case of our example, it should be the value of CLOSE_WINDOW, so that's MyCustomEvent.closeWindow.
The type should be the classname with the full package, in the case of our example it would be 'com.website.events.MyCustomEvent'.
Finally, the meta data looks like this:
[Event(name="MyCustomEvent.closeWindow", type="com.website.events.MyCustomEvent")]
BTW I have some tips about your code:
I would suggest to use English function names and parameters, instead of Dutch.
verbergAdminComponent isn't a good name for a handler, it should be something like handleCloseWindow(event), which should call the verbergAdminComponent function.

Calling a function from one class, from another

I'm having a slight issue with ActionScript 3 and I have come here to ask for some help.
I have two classes. One called Sledge and one called Sock, there is also the document class called Main.
My issues are as follows:
Inside of Sledge, I call a function that is defined inside of the Main document class. How would I go about telling the class to go to the document class and run that function? Would this also be the same for other classes or just for the document class?
Inside Sledge, I have the following statement: if(hitTestObject(sock.myHitArea)) { /* somecode*/ }
sock is an instance of another seperate class, and by this point has already been created. However when I try and run this I am told it is not defined. How would i go about solving this?
There's some ambiguity issues with how you expressed your question. It would help if you posted a short form of the code for the problem.
However, I'll try to answer the first question:
Inside of Sledge, I call a function that is defined inside of the Main document class. How would I go about telling the class to go to the document class and run that function?
You would want to pass the Main class to the Sledge class or use events which is preferable. If pass the class it will look like this...
class Sledge {
private var main:Main;
function Sledge(main:Main) {
this.main = main;
}
function doSomething():void {
main.runSomeFunction();
}
}
Or if using events:
class Main {
private var sledge:Sledge;
function Main() {
sledge = new Sledge();
sledge.addEventListener("mainDoSomething", doSomething);
}
private function doSomething(e:Event):void {
// .... do stuff
}
}
class Sledge extends EventDispacter {
function Sledge() {
}
public function doSomething():void {
dispatchEvent(new Event("mainDoSomething"));
}
}