JSON errors on ActionScript3 - json

I have this code for a mock up mobile app on Flash:
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.*;
import flash.net.*;
import com.adobe.serialization.json.*;
public class ScreenCategories extends Sprite
{
private var lastButtonEndedY:int;
private var categories:Array;
private var dataUrl:String = "getCategories.php";
// Change this to false to use local static data instead;
private var useRemoteData:Boolean = true;
// Constructor: Create an array of three categories
public function ScreenCategories()
{
lastButtonEndedY = 35;
if ( useRemoteData )
{
loadCategories();
}
}
public function loadCategories():void
{
var request:URLRequest = new URLRequest(dataUrl);
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, categoryLoad_complete);
loader.load(request);
}
private function categoryLoad_complete(evt:Event):void
{
var jsonText:String = evt.target.data;
//Deserialize JSON
categories = JSON.decode(jsonText) as Array;
}
private function initializeCategory():void
{
// for each "category" in our list (Array)...
for (var count in categories)
{
// Create a button for each of the categories that exist in our Array
var aCategory:BtnCategory = new BtnCategory(categories[count].category);
// Add the BtnCategory to the stage
aCategory.x = 0;
aCategory.y = lastButtonEndedY;
aCategory.name = categories[count].id; // give it a unique name!
addChild(aCategory);
lastButtonEndedY += (aCategory.getHeight() + 1);
}
addEventListener(MouseEvent.CLICK, mouseClicked);
}
public function mouseClicked(e:MouseEvent)
{
removeEventListener(MouseEvent.CLICK, mouseClicked);
// trace the name of the button that was pressed, in this case - the category number!
var btnPressed:int = parseInt(e.target.name);
// if the category exists in the Array (ie. not undefined), then display that category!
if (categories[btnPressed] != undefined)
{
displayCategory(btnPressed)
}
addEventListener(MouseEvent.CLICK, mouseClicked);
}
// We want to go forward to see the list of products in this category, call back to Main.as so make that happen.
public function displayCategory(categoryId)
{
trace ("display product category:" + categoryId);
var par:* = parent;
par.displayCategory(categoryId);
}
}
}
But I get the following errors:
**JSONParseError: Unexpected < encountered
at com.adobe.serialization.json::JSONTokenizer/parseError()[/Users/rominamora/Documents/AOD/term 3/interactive design/assignment3/com/adobe/serialization/json/JSONTokenizer.as:704]
at com.adobe.serialization.json::JSONTokenizer/getNextToken()[/Users/rominamora/Documents/AOD/term 3/interactive design/assignment3/com/adobe/serialization/json/JSONTokenizer.as:206]
at com.adobe.serialization.json::JSONDecoder/nextToken()[/Users/rominamora/Documents/AOD/term 3/interactive design/assignment3/com/adobe/serialization/json/JSONDecoder.as:102]
at com.adobe.serialization.json::JSONDecoder()[/Users/rominamora/Documents/AOD/term 3/interactive design/assignment3/com/adobe/serialization/json/JSONDecoder.as:71]
at com.adobe.serialization.json::JSON$/decode()[/Users/rominamora/Documents/AOD/term 3/interactive design/assignment3/com/adobe/serialization/json/JSON.as:95]
at ScreenCategories/categoryLoad_complete()[/Users/rominamora/Documents/AOD/term 3/interactive design/assignment3/ScreenCategories.as:45]
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at flash.net::URLLoader/onComplete()**

You probably receive invalid JSON from getCategories.php.
Try running the getCategories.php script in a browser (by pasting the (full) URL in the address bar) and copying the JSON you get into the jsonlint validator. This should give you a heads up about anything invalid.
For a definition of valid JSON check out json.org

Related

Unable to clone vector with deep copy of objects

This is my vector that I want to fully clone (meaning that if I change the cloned vector it doesn't affect the original vector).
var vector:Vector.<Path_Tiles> = new Vector.<Path_Tiles>();
vector = path_finder.Find_Path(Gird.TILE_SET, start, end, R_G_B, SCAREDNESS);// return a vector of path_tiles in order
and I'm trying to put it into this vector
var vector2:Vector.<Path_Tiles> = clone(vector);
and clone method is this (which I found this method on a website so I do not fully understand it)
public function clone(source:Object):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}
But I'm getting this error: "[Fault] exception, information=TypeError: Error #1034: Type Coercion failed: cannot convert AS3.vec::Vector.#85973d1 to AS3.vec.Vector.."
How do I convert Path_Tiles into an object?
Assure your Path_Tiles class has been registered:
flash.net.registerClassAlias("tld.domain.package.Path_Tiles", Path_Tiles);
Then, you may copy by serializing the data to a ByteArray:
var tiles:Vector.<Path_Tiles>;
var tilesCloned:Vector.<Path_Tiles>;
var byteArray = new ByteArray();
byteArray.writeObject(tiles);
byteArray.position = 0;
tilesCloned = byteArray.readObject() as Vector.<Path_Tiles>;
Cast the readObject() deserialization to Vector.<Path_Tiles> using the as keyword.
Constructors for objects serialized must accept default parameters.
To put this all together, say this was your Path_Tiles class:
Path_Tiles.as
package
{
public class Path_Tiles
{
public function Path_Tiles(property1:String=null, property2:int=undefined) {
this.property1 = property1;
this.property2 = property2;
}
public var property1:String;
public var property2:int;
}
}
Here is your main class, showing an example of deep cloning the Path_Tiles collection:
Main.as
package
{
import flash.display.Sprite;
import flash.net.registerClassAlias;
import flash.utils.ByteArray;
public class Main extends Sprite
{
public function Main() {
super();
var source:Vector.<Path_Tiles> = new <Path_Tiles>[
new Path_Tiles("Hello", 1),
new Path_Tiles("World", 2)
];
var cloned:Vector.<Path_Tiles> = clone(source);
}
public function clone(source:Vector.<Path_Tiles>):Vector.<Path_Tiles> {
flash.net.registerClassAlias("Path_Tiles", Path_Tiles);
var byteArray = new ByteArray();
byteArray.writeObject(source);
byteArray.position = 0;
return byteArray.readObject() as Vector.<Path_Tiles>;
}
}
}
Finally, we can see the object was deep copied; confirmed by memory address:

Read/write as3 zip using fzip

I'm creating a custom file type in AIR, which is a zip file under a different extension. I've been trying a few libs out, and settled on Fzip. This is to house my apps project files.
The tests seem to run OK, apart from occasionally getting 'Unknown record signature' Error.
I'm wondering if I'm missing something, and perhaps someone can shed some light. First time I've attempted something like this.
It seems to occur randomly, I have a basic app which allows you to add new files at runtime. Contents display in a list and upon select you view the text content.
From time to time when adding a new file, saving, then reopening I get this unknown record error. The main functions which could be the cause
private function openComplete( event:Event ):void {
_zipFile.loadBytes( _file.data );
dispatch( new ZipServiceEvent( ZipServiceEvent.CONTENTS_CHANGE ) );
}
public function saveFile( event:Event=null ):void {
if( _file.isDirectory ) {
browseForSave();
return void;
}
if ( _file.extension != _ext )
_file = new File( _file.nativePath + _ext );
var stream:FileStream = new FileStream();
stream.open( _file, FileMode.WRITE );
_zipFile.serialize( stream );
stream.close();
}
public function getFile( name:String ):FZipFile {
return _zipFile.getFileByName( name );
}
public function addFile( name:String, contents:ByteArray ):void {
_zipFile.addFile( name, contents );
}
private function saveFileHandler( event:Event ):void {
var contents:ByteArray = new ByteArray();
contents.writeMultiByte( view.filecontents.text, 'utf-8' );
model.addFile( view.filename.text, contents );
}
I would need to test you class to look for errors… I wont have time to check it but in the meanwhile I´ll post an Util class I created for this purpose. It´s not very extensive, it was just for a small project but it may help you…
package com.models
{
import com.events.AppEvent;
import deng.fzip.FZip;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.ByteArray;
public class ZIPEncoder extends EventDispatcher
{
private var _zip:FZip;
private var _compressedBytes:ByteArray = new ByteArray();
public function ZIPEncoder(target:IEventDispatcher=null)
{
super(target);
}
public function newZip(name:String = ""):void
{
if(_zip) _zip = null;
_zip = new FZip();
_zip.addEventListener(Event.COMPLETE, onZipComplete);
}
public function newEntry(name:String, bytes:ByteArray):void
{
if(_zip == null)
{
throw(new Error("No zipOutput initialized. Call newZip() to initialize a new ZipOutput object before creating entry instances"));
return;
}
_zip.addFile(name, bytes);
}
public function compress():void
{
_zip.serialize(_compressedBytes, false);
dispatchEvent(new AppEvent(AppEvent.ZIP_ENCODED, _compressedBytes));
}
private function onZipComplete(event:Event):void
{
dispatchEvent(new AppEvent(AppEvent.ZIP_ENCODED, _compressedBytes));
}
//public function get zip():ZipOutput { return _zip; }
}
}
hope it helps…

Initialize Assets after Preload

Whenever I export the .swf file of my Flash game, I am receiving "TypeError: Error #1009: Cannot access a property or method of a null object reference.", along with a Runtime Shared Library Preloading Warning for my preloader. I have my timeline organized so that the first and third frames are both empty along with a stop(); command in the Actions layer. The second frame contains a single MovieClip that contains all of my exported assets, which are going to be initialized in the third frame of the timeline. None of my assets, except for the preloader, are exported in the first frame. What changes should I make to my Document Class for it to initialize the assets in the third frame?
Document Class:
package com.gameEngine.documentClass
{
import flash.events.*;
import flash.display.*;
import flash.geom.Point;
import com.gameEngine.assetHolders.*;
import com.gameEngine.assetHolders.Levels.*;
public class Document extends MovieClip
{
private static var _document:Document;
private var preloader:Preloader;
public var mcMain:Player;
public var restartButton:RestartButton;
public var spawnArea:SpawnArea;
public var level_1:Level_1;
public var level_2:Level_2;
public var level_3:Level_3;
public function Document()
{
addEventListener(Event.ADDED_TO_STAGE, init);
_document = this;
preloader = new Preloader(390, this.loaderInfo);
this.addChild(preloader);
preloader.addEventListener("loadComplete", loadAssets);
preloader.addEventListener("preloaderFinished", showLogo);
mcMain = new Player(this);
restartButton = new RestartButton(this);
spawnArea = new SpawnArea();
level_1 = new Level_1(this);
level_2 = new Level_2(this);
level_3 = new Level_3(this);
this.addChild(restartButton);
this.addChild(spawnArea);
this.preloader.x = 400;
this.preloader.y = 250;
restartButton.x = 822.95;
restartButton.y = 19;
spawnArea.x = 400;
spawnArea.y = 250;
trace ("Document Class Initialized");
// constructor code
}
public static function getInstance():Document
{
return _document;
}
private function loadAssets(event:Event):void
{
this.play();
}
private function showLogo(event:Event):void
{
this.removeChild(preloader);
}
public function init(event:Event)
{
if (stage.contains(spawnArea))
{
addChild(mcMain);
}
mcMain.x = spawnArea.x;
mcMain.y = spawnArea.y;
}
}
}
Preloader Class:
package com.gameEngine.assetHolders
{
import com.gameEngine.documentClass.*;
import flash.display.*;
import flash.events.*;
public class Preloader extends MovieClip
{
private var fullWidth:Number;
public var loaderInfo:LoaderInfo;
public function Preloader(fullWidth:Number = 0, loaderInfo:LoaderInfo = null)
{
this.fullWidth = fullWidth;
this.loaderInfo = loaderInfo;
addEventListener(Event.ENTER_FRAME, checkLoad);
}
private function checkLoad (event:Event):void
{
if (loaderInfo.bytesLoaded == loaderInfo.bytesTotal && loaderInfo.bytesTotal != 0)
{
dispatchEvent(new Event("loadComplete"));
phaseOut();
}
updateLoader(loaderInfo.bytesLoaded / loaderInfo.bytesTotal);
}
private function updateLoader(num:Number):void
{
progressBar.width = num * fullWidth;
}
private function phaseOut():void
{
removeEventListener(Event.ENTER_FRAME, checkLoad);
progressBar.gotoAndPlay(2);
if (progressBar.currentFrame == progressBar.totalFrames)
{
phaseComplete();
}
}
private function phaseComplete() : void
{
dispatchEvent(new Event("preloaderFinished"));
}
}
}
You have a lot of race conditions going on here. Many of these events could occur at relatively random times in relation to one another . . . you have to think asynchronously. That is, there can be no assumption that any object exists. E.g., in Document.init(), you check is if the spawnArea exists, but it is almost guaranteed not to at that point, and you never check for it again.
Without making any specific changes, I can recommend a generic solution. For any object (objB) you want loaded after another object (objA) is loaded, have objB created in the objA's ADDED_TO_STAGE handler. A simple example would be:
var objA:Whatever;
var objB:WhateverElse;
[...]
objA = new Whatever();
objA.addEventListener(Event.ADDED_TO_STAGE, objAAddedHnd);
[...]
public function objAAddedHnd(event:Event)
{
// remove the event, if no longer needed:
objA.removeEventListener(Event.ADDED_TO_STAGE, objAAddedHnd);
objB = new WhateverElse();
objB.addEventListener(Event.ADDED_TO_STAGE, objBAddedHnd);
}
[...]
public function objBAddedHnd(event:Event)
{
// remove the event, if no longer needed:
objB.removeEventListener(Event.ADDED_TO_STAGE, objBAddedHnd);
// and so on . . .
}
At this point, it shows that you would need to plan the timeline of object creation.

ActionScript throwing Error #1009 when calling addChild with a TileList as the argument

To be exact this is the error.
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at fl.containers::BaseScrollPane/drawBackground()
at fl.controls::TileList/draw()
at fl.core::UIComponent/callLaterDispatcher()
Now I've tried several of Adobe's own examples from this page, http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/fl/controls/TileList.html, and they all throw this error as well.
The error is triggered by the TileList instance being the argument of the addChild function.
Here's my package, which works fine when I change the displayComponent is be a List.
package com.pennstate {
import fl.data.DataProvider;
import flash.display.MovieClip;
import flash.display.DisplayObjectContainer;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.text.TextFormat;
import flash.xml.XMLDocument;
import com.adobe.serialization.json.JSON;
import fl.controls.List;
import fl.controls.TileList;
public class Sign {
public var displayComponent:TileList;
public var url:String;
public var provider:DataProvider;
public var mc:MovieClip;
public var container:DisplayObjectContainer;
public function Sign( url:String, container ) {
this.container = container;
this.displayComponent = new TileList();
this.mc = new MovieClip();
this.url = url;
this.provider = new DataProvider();
_componentSetup();
loadJson();
_componentFormat();
}
private function _componentSetup() {
displayComponent.labelFunction = getLabelFieldContent;
displayComponent.sourceFunction = getSourceFieldContent;
displayComponent.dataProvider = provider;
displayComponent.selectable = false;
displayComponent.setStyle("contentPadding", 5);
displayComponent.setSize(1720,770);
displayComponent.move(100,200);
displayComponent.rowHeight = 190;
trace('End setup');
}
private function _componentFormat() {
var listTextFormat:TextFormat = new TextFormat();
listTextFormat.font = "Arial";
listTextFormat.color = 0x000000;
listTextFormat.bold = true;
listTextFormat.size = 48;
displayComponent.setRendererStyle("textFormat", listTextFormat);
trace('End formatting');
}
function loadJson():void {
var jsonLoader:URLLoader = new URLLoader();
jsonLoader.addEventListener(Event.COMPLETE, onJsonComplete);
jsonLoader.load( new URLRequest( url ) );
}
function onJsonComplete(e:Event):void {
trace('Loading finished.');
var jsonData:String = e.target.data;
trace(jsonData + "\n");
var decodedData = JSON.decode(jsonData, false);
for (var index in decodedData.rows) {
provider.addItem({title: decodedData.rows[index].node.title, result: decodedData.rows[index].node.Result});
trace(index+" => "+decodedData.rows[index].node.title);
trace(index+" => "+decodedData.rows[index].node.Result);
}
container.addChild(displayComponent);
}
function getLabelFieldContent(item:Object):String {
return new XMLDocument(item.title + "\n" + item.result).firstChild.nodeValue;
}
function getSourceFieldContent(item:Object):String {
return item.result;
}
}
}
You have not given your container agrument in the constructor a type i.e: UIComponent
public function Sign( url:String, container:UIComponent )
This coupled with the fact that its the same name as your member variable is probably the cause.
I had to drag an actual TileList component from the Component Menu onto the Stage using the Flash CS4 GUI to make this error go away.
The weird part is the component that I dragged onto the Stage isn't the component I use in the code. The component I created dynamically in the code now works though.
I even deleted the TileList component that I added to the Stage and it still works. This sounds like a bug to me.

Run-time loading of external assets and re-using preloaded assets in actionscript 3?

I'm creating a 2d flash game (coded in flex/actionscript 3) where assets are downloaded when they are needed. Currently I have it setup like this:
AssetLoader.as
package
{
import flash.display.Loader;
import flash.net.URLRequest;
public class AssetLoader extends Loader
{
//set vars
private var url:String = "http://test.com/client/assets/";
public function AssetLoader(url:String)
{
Logger.log("AssetLoader request: " + this.url + url);
var request:URLRequest = new URLRequest(this.url + url);
this.load(request);
}
}
}
Then, where I want to load the asset I do the following:
var asset:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
asset.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete, false, 0, true);
private function onShipAssetComplete(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
shipImage = Bitmap(loader.content);
shipImage.smoothing = true;
addChild(shipImage);
}
The thing is, that this method doesn't check for already downloaded assets, so it will redownload them the second time the same asset is being requested (I think).
So, what I need is an array where all downloaded assets are stored, and on request the name of this asset is checked for existance in the array. So if it has already been downloaded, that asset from memory must be returned rather than redownloaded.
I could make the assetloader a static class, but I have to wait for the event to fire when it's done downloading the image - so I can't simply let a static function return the corresponding image. Any idea how I should do this?
EDIT for an attempt after comments:
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
public final class AssetManager
{
private static var assets:Object = {};
private static var preUrl:String = Settings.ASSETS_PRE_URL;
public static function load(postUrl:String):*
{
if (assets[postUrl])
{ //when the asset already exists
//continue
}
else
{ //the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
assets[postUrl] = loader.content;
}, false, 0, true);
}
}
}
}
EDIT2: another attempt
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLRequest;
public final class AssetManager
{
private static var assets:Object = {};
private static var preUrl:String = Settings.ASSETS_PRE_URL;
public static function load(postUrl:String):*
{
if (assets[postUrl])
{ //the asset already exists
var dispatcher:EventDispatcher = new EventDispatcher();
dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
}
else
{ //the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event):void
{
var loader:Loader = Loader(event.target.loader);
assets[postUrl] = loader.content;
var dispatcher:EventDispatcher = new EventDispatcher();
dispatcher.dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, assets[postUrl]));
}, false, 0, true);
}
}
}
}
Then, I try the following:
var asset:AssetManager = AssetManager.load("ships/" + graphicId + ".gif");
asset.addEventListener(CustomEvent.LOAD_COMPLETE, onShipAssetComplete, false, 0, true);
But get an error, "undefined method addEventListener by a reference of the type static AssetManager" (roughly translated).
You could add a static object (used as a dictionary with urls for assets as keys and the content for assets as values) in the AssetLoader class and in the same time keep using the class in the way you're using it right now.
private static var assets:Object = {};
The difference would be that your class would need to check against that static object if the URL for the content has already been requested previously. If it has, dispatch the complete event immediately. If it hasn't, follow the normal routine and don't forget to populate your static object with the newly loaded asset.
Update:
This is a quick example of what I meant. I haven't had time to test this, but it should work.
Note:
You must invoke the loadAsset() method of the AssetLoader instances you create in order to actually load the asset. This is consistent with the way the Loader class we're extending works.
You should always add all event listeners BEFORE invoking the loadAsset() method. In your question you're calling the load() method from within the constructor and only afterwards add the event listener for Event.COMPLETE. This could produce strange results.
Here's the code:
package
{
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
public class AssetLoader extends Loader
{
private static const BASE_URL:String = 'http://test.com/client/assets/';
public static var storedAssets:Object = {};
private var assetURL:String;
private var urlRequest:URLRequest;
private var cached:Boolean = false;
public function AssetLoader(url:String):void
{
trace('Loading: ' + url);
assetURL = url;
if (storedAssets[assetURL] != null)
{
cached = true;
trace('Cached');
}
else
{
trace('Loading uncached asset');
urlRequest = new URLRequest(BASE_URL + assetURL);
contentLoaderInfo.addEventListener(Event.COMPLETE, OnAssetLoadComplete);
}
}
public function loadAsset():void
{
if (cached)
loadBytes(storedAssets[assetURL]);
else
load(urlRequest);
}
private function OnAssetLoadComplete(event:Event):void
{
storedAssets[assetURL] = contentLoaderInfo.bytes;
trace('Loaded ' + contentLoaderInfo.bytesLoaded + ' bytes');
}
}
}
Update 2:
Here's how one would use the class above:
var assetLdr:AssetLoader = new AssetLoader("ships/" + graphicId + ".gif");
assetLdr.contentLoaderInfo.addEventListener(Event.COMPLETE, onShipAssetComplete);
assetLdr.loadAsset();
private function onShipAssetComplete(event:Event):void
{
var shipImage:Bitmap = Bitmap(event.target.loader.content);
// Do stuff with shipImage
}
Perhaps you should take a look at Bulk Loader. It does the kinds of things your looking to do. If you really want to use a custom solution, it would be a great point of reference, but why reinvent the wheel?
Tyler.
Here is an alteration of your load command to capture the resourceId
public function load(postUrl:String):*
{
var index:int;
if ((index = assetExists(postUrl)) != -1)
{
dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
}
else
{
//the asset still has to be downloaded
var request:URLRequest = new URLRequest(preUrl + postUrl);
var loader:Loader = new Loader();
loader.load(request);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(event:Event)
{
// NOTE: not sure on scoping here ...
// pretty sure you're this will be the Loader
// if not just read it off the event like you were before
assets[postUrl] = content;
dispatchEvent(new CustomEvent(CustomEvent.LOAD_COMPLETE, asset[postUrl]));
}, false, 0, true);
}
}
/* In a new file */
public class CustomEvent extends Event
{
public static const LOAD_COMPLETE:String = "CustomEvent_LoadComplete";
// If you know the type you should use it (e.g. Sprite/DisplayObject)
public var content:*;
public function CustomEvent(type:String, _content:*)
{
content = _content;
super(type);
}
}
Note: when you write an Event descendant you should also override the toString and clone methods. I've also cheated on the constructor since you may want to pass through weakReferences and things like that.