Why is object property null? - apache-flex

Below is my class, which simply reads an xml file and provides the contents in e4x format. Unfortunately, after the constructors executes and sets the xmlProperties property with the expected values, it some how becomes null. Anyone know what I'm doing wrong?
public class WebService
{
private var _propertiesReader:HTTPService;
private var _xmlProperties:XML;
public function WebService()
{
_propertiesReader = new HTTPService();
_propertiesReader.url = "../resources/properties.xml";
_propertiesReader.resultFormat = "e4x";
_propertiesReader.contentType = "application/xml";
_propertiesReader.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void
{
_xmlProperties = XML(event.result);
});
_propertiesReader.addEventListener(FaultEvent.FAULT, function(event:FaultEvent):void
{
Alert.show("Unable to load properties content: " + event.fault.message + "\nPlease try again later.", "Properties File Load Error");
});
_propertiesReader.send();
}
public function get xmlProperties():XML
{
return _xmlProperties;
}
}

_xmlProperties is being set by a File Load call (via a callback event). It is not being set directly in the constructor.
Are you sure you are waiting for the call to finish and the callback event to fire before you check the value of _xmlProperty?

Related

Flex applying the sort/filter on an arraycollection without dispatching event

I have a object that is extended from arraycollection. This object has to access and manipulate the arraycollections source object. When this happens, the local sorted/filter copy of data goes out of sync with the source data. To line things up correctly, the sort/filter needs to be re-applied.
To do this normally, you would call refresh() on the arraycollection, but this also broadcasts a refresh event. What I want is to update the sort/filter without dispatching an event.
Having looked into the ArrayCollection class, I can see it is extended from ListCollectionView. The refresh function
public function refresh():Boolean
{
return internalRefresh(true);
}
is in ListCollectionView and it calls this function
private function internalRefresh(dispatch:Boolean):Boolean
{
if (sort || filterFunction != null)
{
try
{
populateLocalIndex();
}
catch(pending:ItemPendingError)
{
pending.addResponder(new ItemResponder(
function(data:Object, token:Object = null):void
{
internalRefresh(dispatch);
},
function(info:Object, token:Object = null):void
{
//no-op
}));
return false;
}
if (filterFunction != null)
{
var tmp:Array = [];
var len:int = localIndex.length;
for (var i:int = 0; i < len; i++)
{
var item:Object = localIndex[i];
if (filterFunction(item))
{
tmp.push(item);
}
}
localIndex = tmp;
}
if (sort)
{
sort.sort(localIndex);
dispatch = true;
}
}
else if (localIndex)
{
localIndex = null;
}
revision++;
pendingUpdates = null;
if (dispatch)
{
var refreshEvent:CollectionEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
refreshEvent.kind = CollectionEventKind.REFRESH;
dispatchEvent(refreshEvent);
}
return true;
}
annoyingly, that function is private and so is unavailable to and class that extends ListCollectionView. Also, a lot of what is in the internalRefresh function is private too.
Does anyone know of a way to call internalRefresh from a class that extends ArrayCollection? Or a way of stopping the refresh event from being dispatched when refresh is called?
My (read:hack) solution to this:
addEventListener(CollectionEventKind.REFRESH, handlerHack, true);
The true adds this listener onCapture, before anyone else gets to act on the event.
Before you call the collection.refresh() to update sort/filter, set a boolean flag to true.
discardRefreshEvent = true;
myCol.refresh();
In the listener...
private function handlerHack(evt:CollectionEvent):void
{
if (discardRefreshEvent)
{
evt.stopImmediatePropagation();
discardRefreshEvent = false;
}
}
Disclaimer: Haven't done this exact use before (have implemented similar functionality with other events), also only guessing on Event types/names.
maybe you could extend ArrayCollection, listen to the refresh event and call stopImmediatePropagation() on it when it is fired ? I would start with this...
Good luck :-)

Actionscript 3 Null Object Error Message

I am building a AS3 only project and got runtime error that said "Cannot access a property or method of a null object reference."
Here is my code:
main.as
public class videoMain extends Sprite{
private var videoPlayer:Player;
public function videoMain (){
loadPlayer()
loadProgress();
}
private function loadProgress():void{
//the code below gave me null object error.....
var byteLoaded:Number=videoPlayer.videoBytesLoaded; //the problem code
var byteTotal:Number=videoPlayer.videoBytesTotal; //the problem code
var percent:Number=Math.floor(byteLoaded/byteTotal)*100;
}
private function loadPlayer():void{
videoPlayer= new Player();
videoPlayer.createPlayer();
}
}
Player.as
public function createPlayer():void{
_loader = new Loader();
_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);
_loader.load(new URLRequest(playerType));
}
public function get videoBytesLoaded():Number{
return _Player.getVideoBytesLoaded(); //youtube api method
}
public function get videoBytesTotal():Number{
return _Player.getVideoBytesTotal; //youtube api method
}
private function onLoaderInit(event:Event):void {
_Player=_loader.content;
//only show part of codes....
}
I appreciate any help....Thanks!!!!!
_Player is only defined after the Event.INIT has fired so any call before the _Player value is defined will throw an error.
You should, at the minimum , have this:
public function videoMain (){
loadPlayer()
}
private function onLoaderInit(event:Event):void {
_Player=_loader.content;
//only show part of codes....
loadProgress();
}
but progress events are not static so really you should have an enterFrame event listener in order to listen to the changing values...
private function onLoaderInit(event:Event):void {
_Player=_loader.content;
//only show part of codes....
addEventListener(Event.ENTER_FRAME , enterFrameListener);
}
private function enterFrameListener(event:Event):void
{
loadProgress();
// and here you add some way to remove this event listener when
// the video is fully loaded
}

Returning from Flex/ActionScript 3 Responder objects

I need to return the value from my Responder object. Right now, I have:
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
Basically, I need to know how to get the return value of ret_pr into id or anything that I return from that function. The responder just seems to eat it. I can't use a public variable somewhere else because this will be running multiple times at once, so I need local scope.
This is how I'd write a connection to the AMF server, call it and store the resulting value. Remember that the result won't be available instantly so you'll set up the responder to "respond" to the data once it returns from the server.
public function init():void
{
connection = new NetConnection();
connection.connect('http://10.0.2.2:5000/gateway');
setSessionID( 1 );
}
public function setSessionID(user_id:String):void
{
var amfResponder:Responder = new Responder(setSessionIDResult, onFault);
connection.call("ServerService.setSessionID", amfResponder , user_id);
}
private function setSessionIDResult(result:Object):void {
id = result.id;
// here you'd do something to notify that the data has been downloaded. I'll usually
// use a custom Event class that just notifies that the data is ready,but I'd store
// it here in the class with the AMF call to keep all my data in one place.
}
private function onFault(fault:Object):void {
trace("AMFPHP error: "+fault);
}
I hope that can point you in the right direction.
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
This code is never going to get you what you want. You need to use a proper result function. The anonymous function responder return value will not be used by the surrounding function. It will always return 0 in this case. You are dealing with an asynchronous call here, and your logic needs to handle that accordingly.
private function pro():void {
gateway.connect('http://10.0.2.2:5000/gateway');
var responder:Responder = new Responder(handleResponse);
gateway.call('sx.xj', responder);
}
private function handleResponse(result:*):void
{
var event:MyCustomNotificationEvent = new MyCustomNotificationEvent(
MyCustomNotificationEvent.RESULTS_RECEIVED, result);
dispatchEvent(event);
//a listener responds to this and does work on your result
//or maybe here you add the result to an array, or some other
//mechanism
}
The point there being using anon functions/closures isn't going to give you some sort of pseudo-syncronous behavior.

How do I create a Hierarchical Cursor from the dataProvider of an AdvancedDataGrid?

In a previous application that I had written, I had a Class that extended AdvancedDataGrid (ADG). It contained the following code:
package
{
public class CustomADG extends AdvancedDataGrid
{
....
// This function serves as the result handler for a webservice call that retrieves XML data.
private function webServiceResultHandler(event:ResultEvent):void
{
var resultXML:XML = new XML(event.result);
dataProvider = new HierarchicalData(resultXML.children);
}
....
public function setOpenNodes(maxDepth:int = 0):void
{
var dataCursor:IHierarchicalCollectionViewCursor = dataProvider.createCursor();
while (dataCursor.current != null)
{
if (dataCursor.currentDepth < maxDepth)
dataProvider.openNode(dataCursor.current);
dataCursor.moveNext();
}
dataProvider.refresh();
}
}
}
In this implementation, the function setOpenNodes() worked fine - it did exactly what I intended it to do - pass it a number, and open all nodes in the dataProvider at or below that level.
Now, I am creating a new ADG Class and want to reproduce this functionality:
package view
{
import mx.collections.IHierarchicalCollectionViewCursor;
public class ReportADG extends AdvancedDataGrid
{
public function ReportADG()
{
super();
}
public function setOpenNodes(maxDepth:int = 0):void
{
var dataCursor:IHierarchicalCollectionViewCursor =
dataProvider.createCursor();
while (dataCursor.current != null)
{
if (dataCursor.currentDepth < maxDepth)
dataProvider.openNode(dataCursor.current);
dataCursor.moveNext();
}
dataProvider.refresh();
}
}
}
The dataProvider is set in the parent component:
<view:ReportADG id="reportADG" dataProvider="{reportData}" />
reportData is set in another file:
reportData = new HierarchicalData(resultXML.children);
However, I am getting runtime errors:
TypeError: Error #1034: Type Coercion failed: cannot convert ListCollectionViewCursor#6f14031 to mx.collections.IHierarchicalCollectionViewCursor.
I've tried casting dataProvider as ICollectionView. I've tried then casting the ICollectionView as IHierarchicalCollectionView. I've tried all sorts of casting, but nothing seems to work. Why won't this work in this new implementation as it did in the past implementation? What do I need to do?
*** Update:
I started debugging this. I added an override setter to my ADG Class to see when dataProvider was being set:
override public function set dataProvider(value:Object):void
{
super.dataProvider = value;
}
I added a breakpoint to this setter and to my setOpenNodes() function. Sure enough, the dataProvider is being set BEFORE setOpenNodes() is called, and it is HierarchicalData. But, when the setOpenNodes() the debugger says that the dataProvider is a null ArrayCollection. It seems like this is the root issue.
I needed to call commitProperties before attempting to access the dataProvider property.
public function setOpenNodes(maxDepth:int = 0):void
{
super.commitProperties();
var dataCursor:IHierarchicalCollectionViewCursor =
dataProvider.createCursor();
while (dataCursor.current != null)
{
if (dataCursor.currentDepth < maxDepth)
dataProvider.openNode(dataCursor.current);
dataCursor.moveNext();
}
dataProvider.refresh();
}

How can I test a SWF URL before Loading Styles From if (or catching the error)?

I am trying to use the following code to load styles from an external SWF, but I keep getting an ActionScript error when the URL is invalid:
Error: Unable to load style(Error #2036: Load Never Completed. URL: http://localhost/css/styles.swf): ../css/styles.swf.
at <anonymous>()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\styles\StyleManagerImpl.as:858]
private function loadStyles(): void
{
try
{
var styleEvent:IEventDispatcher =
StyleManager.loadStyleDeclarations("../styles.swf");
styleEvent.addEventListener(StyleEvent.COMPLETE,
loadStyle_completeHandler);
styleEvent.addEventListener(StyleEvent.ERROR,
loadStyle_errorHandler);
}
catch (error:Error)
{
useDefault();
}
}
private function loadStyle_completeHandler(event:StyleEvent): void
{
IEventDispatcher(event.currentTarget).removeEventListener(
event.type, loadStyle_completeHandler);
goToNextStep();
}
private function loadStyle_errorHandler(event:StyleEvent): void
{
IEventDispatcher(event.currentTarget).removeEventListener(
event.type, loadStyle_errorHandler);
useDefault();
}
I basically want to go ahead an use the default styles w/o the user seeing the error if this file can't be loaded - but I can't seem to find any way to do this.
Interesting problem. Try removing the removeEventListener call, or commenting it out; in my brief tests it appeared the event handler was being called twice (I'm not immediately sure why, although I suspect it has to do with style inheritance), and commenting that line did the trick.
If you have the same result, you might try just checking for the listener (using hasEventListener) first, before attaching it in your loadStyles() function, instead. Hope it helps!
** Not an answer, but an update:
FYI, this is the ActionScript code in the Source of mx.styles.StyleManagerImpl that is run when you call StyleManager.loadStyleDeclarations(). I ran the debugger and added a breakpoint at the Line 858("throw new Error(errorText);"), and the breakpoint was caught. I'm thinking it shouldn't be caught there, but the previous IF ("if (styleEventDispatcher.willTrigger(StyleEvent.ERROR))") should be run instead.
public function loadStyleDeclarations2(
url:String, update:Boolean = true,
applicationDomain:ApplicationDomain = null,
securityDomain:SecurityDomain = null):
IEventDispatcher
{
var module:IModuleInfo = ModuleManager.getModule(url);
var readyHandler:Function = function(moduleEvent:ModuleEvent):void
{
var styleModule:IStyleModule =
IStyleModule(moduleEvent.module.factory.create());
styleModules[moduleEvent.module.url].styleModule = styleModule;
if (update)
styleDeclarationsChanged();
};
module.addEventListener(ModuleEvent.READY, readyHandler,
false, 0, true);
var styleEventDispatcher:StyleEventDispatcher =
new StyleEventDispatcher(module);
var errorHandler:Function = function(moduleEvent:ModuleEvent):void
{
var errorText:String = resourceManager.getString(
"styles", "unableToLoad", [ moduleEvent.errorText, url ]);
if (styleEventDispatcher.willTrigger(StyleEvent.ERROR))
{
var styleEvent:StyleEvent = new StyleEvent(
StyleEvent.ERROR, moduleEvent.bubbles, moduleEvent.cancelable);
styleEvent.bytesLoaded = 0;
styleEvent.bytesTotal = 0;
styleEvent.errorText = errorText;
styleEventDispatcher.dispatchEvent(styleEvent);
}
else
{
throw new Error(errorText);
}
};
module.addEventListener(ModuleEvent.ERROR, errorHandler,
false, 0, true);
styleModules[url] =
new StyleModuleInfo(module, readyHandler, errorHandler);
// This Timer gives the loadStyleDeclarations() caller a chance
// to add event listeners to the return value, before the module
// is loaded.
var timer:Timer = new Timer(0);
var timerHandler:Function = function(event:TimerEvent):void
{
timer.removeEventListener(TimerEvent.TIMER, timerHandler);
timer.stop();
module.load(applicationDomain, securityDomain);
};
timer.addEventListener(TimerEvent.TIMER, timerHandler, false, 0, true);
timer.start();
return styleEventDispatcher;
}
I debugged the source, and found that the ERROR Event is being triggered twice. So, I simply set a flag the first time the ERROR event handler is triggered, and check that flag for a value of true before continuing:
private var isErrorTriggered:Boolean; // Default is false
private function loadStyle_completeHandler(event:StyleEvent):void
{
IEventDispatcher(event.currentTarget).removeEventListener(
event.type, loadStyle_completeHandler);
goToNextStep();
}
private function loadStyle_errorHandler(event:StyleEvent):void
{
if (isErrorTriggered)
return;
isErrorTriggered = true;
useDefault();
}

Resources