Existing class to get property chain in ActionScript3? - actionscript-3

Is there an existing class in Flash or Flex that gets the value from an object from it's property chain?
For example, something like this:
private function labelFunction(item:Object, column:GridColumn):String {
// where dataField equals "fields.status.name"
var value:String = PropertyChain.getValue(field, column.dataField);
return value;
}
~~~ Update ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I found this private method in the Binding class as well that could probably be used in a custom class:
/**
* #private
*/
private static function getFirstWord(destStr:String):String
{
// indexPeriod and indexBracket will be equal only if they
// both are -1.
var indexPeriod:int = destStr.indexOf(".");
var indexBracket:int = destStr.indexOf("[");
if (indexPeriod == indexBracket)
return destStr;
// Get the characters leading up to the first period or
// bracket.
var minIndex:int = Math.min(indexPeriod, indexBracket);
if (minIndex == -1)
minIndex = Math.max(indexPeriod, indexBracket);
return destStr.substr(0, minIndex);
}

I don't think there's an existing function. But it's very easy to build one, and it need not be restricted to generic Object sources, as any member of any object can be retrieved by name using square bracket notation. This simple version doesn't do any validation:
public static function getByName(root:*, member:String):* {
var memlist:Array = member.split('.');
var temp:* = root;
for(var i:uint = 0; i < memlist.length; i++)
temp = temp[memlist[i]];
return temp;
}
// And you can use this even on strongly-typed values, such as a MovieClip:
trace("stageWidth =", SomeUtil.getByName(mc, "stage.stageWidth"));

Related

AS3: How to access children of children of children?

Is there a better (quicker, shorter or neater) way to access a child sprite (object) 3 layers or more deep? If the children have all been given names and you know the names. Is there a better way to do it than the code I made below?
var mySprite:DisplayObjectContainer = layerASprite.getChildByName("layerA") as DisplayObjectContainer;
mySprite = mySprite.getChildByName("layerB") as DisplayObjectContainer;
mySprite.getChildByName("layerC").y = 200;
If they are unique you can create a "global" registry in a form of static class variables/methods:
package
{
public class Registry
{
static private var hash:Object = new Object;
static public function register(name:String, target:DisplayObject):void
{
hash[name] = target;
}
static public function access(name:String):DisplayObject
{
return hash[name];
}
}
}
Usage:
// Any MovieClip, frame 1 or class constructor.
import Registry;
Registry.register("deepChild", this);
// From any other place.
import Registry;
Registry.access("deepChild").y = 200;
Alternately you can use a method that digs children of children by a single string argument:
function offSpring(path:String):DisplayObject
{
var aSplit:Array = path.split(".");
var result:DisplayObject = this;
while (aSplit.length)
{
var aParent:DisplayObjectContainer = result as DisplayObjectContainer;
if (!aParent) return null;
result = aParent.getChildByName(aSplit.shift());
}
return result;
}
Usage:
offSpring("layerA.layerB.layerC").y = 200;
As I'm not a big fan of static properties so I would also propose recursive search:
public function findChild(d:DisplayObject, n:String):DisplayObject {
var dc:DisplayObjectContainer = d as DisplayObjectContainer; if (!dc) return null;
for (var i:int = 0; i < dc.numChildren; i++){
var ch:DisplayObject = dc.getChildAt(i);
if (ch.name == n || (ch = findChild(ch, n))) return ch;
}return null;
}
and than you can simply type this:
var d:DisplayObject = findChild(stage, "childName");
to find first child with childName name anywhere on stage.
I just wrote it and tested it once, but I hope it fine.
Advantages:
You don't need to make any additional steps to work with this method. You don't even need to name containers of child you search for.
You can start search at any DisplayObjectContainer you want.
If you decide at some point that you need you need to move your child form container A to container B no change in code is needed if it's still part of the same branch and has unique name.
Disadvantages:
It could be expensive, especially if you have extensive branch.
You need to make sure name of your child is unique across searched branch.
More sophisticated version
As searched children could usually be direct child of given container or be on closer level you could possibly search for child one level at time though it's bit tricky. For example my alpha version:
/**
* Perform parallel/sprial recursive search in this container to find child with given name.
* This means that this function will first check names of all child of this container and and then go to deeper level.
* In other words, no element will be tested on level x+1 if not all elements were tested on level x.
* This is true for all levels until whole tree is exhausted. This method is using token argument for such functionallity.
* #param n name of child element to be found.
* #param t token passed internally for sipral search. You should not specify this if you want the function to search a whole tree.
* The token has only single value which is basically a desired level at which child should be searched for.
* Level 1 means this function will only check its own childrens, level 2 means that only childs of childs of this container will be checked and so one.
* However note that if you specify a token with some level, only that single level will be searched.
* On the other hand if given token is null, this will check childs on level 1, then level 2, 3... and it will countinue until deepest level has been reached.
* #return nearest child with specified name or null if no child with given name found.
*/
public function findChild(n:String, t:SearchToken = null, ind:String = ""):SGLElement {
ind += " ";
var r:Boolean = (t) ? false : true; //is this call root of search.
t = (t) ? t.offL( -1) : new SearchToken(0); //create new token if not given or decrement current token value.
//trace(ind + "F", this.name, ":", t);
//if (!t) t = new SearchToken(0);
//--t.l;
var cl:SearchToken = new SearchToken(t.l); //current search level.
var exc:int = 0; //exhausted childrens.
if(t.l == 0){//search own children
for (var i:int = 0; i < _childs.length; i++) { //trace(ind + " c", _childs[i].name);
if (_childs[i].name == n) return _childs[i]; }
if (r) ++cl.l; else return null;
}
while( cl.l > 0){
if (exc >= _childs.length) { t.l = -1; return null;}
for (i = 0; i < _childs.length; i++) {
//trace(ind + "ch", t,":", i, _childs[i].name, _childs[i]);
if (!(_childs[i] as SGLElementContainer)) continue;
//trace(ind + "t", t, i);
t.l = cl.l;
var e:SGLElement = SGLElementContainer(_childs[i]).findChild(n, t, ind);
//++cl.l;
if (e) return e;
else if (t.l < 0) exc++;
}
//trace(ind + "END_LEVEL>>>>", t);
if (!r) return null;
//t.l = cl.l;
++cl.l;
}
return null;
}
token class
package adnss.common.utils
{
public class SearchToken
{
/**Current level**/
public var l:int;
public function SearchToken(levelValue:int) {l = levelValue;}
public function toString():String {return String(l);}
/**Set level value and return this token instance.**/
public function setL(v:int):SearchToken { l = v; return this; }
/**Add given offset value to level value and return this token instance.**/
public function offL(v:int):SearchToken { l += v; return this;}
}
}
I note I don't know what is technical name for such search so I gave it my own name and this method is not used for display list so you would need to adapt it. It's bit hard to explain this but if you have some question about it feel free to ask.

How is this function return value being ignored in Actionscript?

I'm trying to port the Box2DFlashAS3 physics engine to another language (Xojo). I am not particularly fluent in Actionscript (but am more so than I am with C++ which Box2D was originally written in).
As I understand it, parameters passed to functions in Actionscript as done so by reference. Consider these two classes (greatly cut down for simplicity, it's the two GetInverse() functions I'm interested in):
public class b2Mat22 {
public function GetInverse(out:b2Mat22) : b2Mat22 {
var a:Number = col1.x;
var b:Number = col2.x;
var c:Number = col1.y;
var d:Number = col2.y;
var det:Number = a * d - b * c;
if (det != 0.0)
{
det = 1.0 / det;
}
out.col1.x = det * d; out.col2.x = -det * b;
out.col1.y = -det * c; out.col2.y = det * a;
return out;
}
public var col1:b2Vec2 = new b2Vec2();
public var col2:b2Vec2 = new b2Vec2();
}
and
public class b2Transform {
public function GetInverse(out:b2Transform = null) : b2Transform {
if (!out)
out = new b2Transform();
R.GetInverse(out.R);
out.position.SetV(b2Math.MulMV(out.R, position));
out.position.NegativeSelf();
return out;
}
public var position:b2Vec2 = new b2Vec2();
public var R:b2Mat22 = new b2Mat22();
}
I don't understand R.GetInverse(out.R); in the b2Transform class. Doesn't the GetInverse() function of the b2Mat22 class return a value? If so, why is it not being used?
That a function returns a value does not mean that this value needs to be used.
Here is an example:
Array - reverse()
Returns: Array — The new array.
Reverses the array in place.
var letters:Array = new Array("a", "b", "c");
trace(letters); // a,b,c
letters.reverse();
trace(letters); // c,b,a
You can see it modifies the array but it STILL returns the new array. This can be used for some techniques like method chaining:
myArray.reverse().concat(...) //you couldn't do this if it didn't return the new array, in that case you would have to do this:
// --->
myArray.reverse();
myArray.concat(...);
Also note that in AS (just like in JS, Java, etc.) the parameter is passed by reference if it is an object but the primitive values are not passed by reference.
Now to say the complete truth I have no idea what those methods from Box2D do, but I believe this might be the case.

Make Objects behave like Radio Buttons in AS3

I would like to implement a very simple way to store a variable containing the last specific "CustomObject" I clicked. I'd like clicks on other objects to be ignored. Take the following sample code for example, given CustomObject extends MovieClip:
//Code within the Document Class:
var square1:CustomObject = new CustomObject();
var square2:CustomObject = new CustomObject();
var square3:CustomObject = new CustomObject();
var triangle1:DifferentObject= new DifferentObject();
square1.x=100; square2.x=200; square3.x=300;
addChild(square1);
addChild(square2);
addChild(square3);
addChild(triangle1);
//Code within the CustomObject Class:
this.addEventListener(MouseEvent.CLICK,radioButtonGlow);
public function radioButtonGlow(e:MouseEvent):void
{
var myGlow:GlowFilter = new GlowFilter();
myGlow.color = 0xFF0000;
myGlow.blurX = 25;
myGlow.blurY = 25;
this.filters = [myGlow];
}
This works great for whenever I click on squares- they light up exactly as expected. However, I'd like to implement a functionality that:
1) Stores the last square I clicked into a variable in the document class
2) Removes the glow from all other squares when I click on another one
Any feedback is greatly appreciated!
I suggest creating a class that acts as a collection of CustomObject instances and manages them in that manner (i.e. ensuring only one of that collection can be selected, etc).
Sample:
public class CustomCollection
{
// Properties.
private var _selected:CustomObject;
private var _items:Array = [];
// Filters.
private const GLOW:GlowFilter = new GlowFilter(0xFF0000, 25, 25);
// Constructor.
// #param amt The amount of CustomObjects that should belong to this collection.
// #param container The container to add the CustomObjects to.
public function CustomCollection(amt:int, container:Sprite)
{
for(var i:int = 0; i < amt; i++)
{
var rb:CustomObject = new CustomObject();
rb.x = i * 100;
_items.push(rb);
container.addChild(rb);
}
}
// Selects a CustomObject at the specified index.
// #param index The index of the CustomObject to select.
public function select(index:int):void
{
for(var i:int = 0; i < _items.length; i++)
{
if(i == index)
{
_selected = _items[i];
_selected.filters = [GLOW];
continue;
}
_items[i].filters = [];
}
}
// The currently selected CustomObject.
public function get selected():CustomObject
{
return _selected;
}
// A copy of the array of CustomObjects associated with this collection.
public function get items():Array
{
return _items.slice();
}
}
Then you can revise your code in the document class to something like:
var collection:CustomCollection = new CustomCollection(3, this);
collection.select(1);
You will need to add your own logic for the click event that deals with selecting the buttons. I suggest adding an index property to each CustomObject as well as a reference to the collection it was added to. That way, you can simply add the click event into the CustomObject class and have the handler function something like:
private function _click(e:MouseEvent):void
{
_collection.select(index);
}

as3 operator= /(object assign )

I know that AS3 does not have pointer or reference. Every object is pass by reference already. (I supposed?)
What should I do if I want to do with pointer?
e.g. all object point to one target, I only need to change target's value, then all other object will access different value.
You can effectively get the same behavior by using a helper object to simulate a pointer -- in other words using it to carry the target reference. For instance:
public class PseudoPointer
{
private var obj:Object;
private var prop:String;
public function PseudoPointer(obj:Object, prop:String)
{
// Point to property with name 'prop' on object 'obj'.
this.obj = obj;
this.prop = prop;
}
public function get value():* {
return obj[prop];
}
public function set value(value:*):void {
obj[prop] = value;
}
}
Then you could do this -- assume there's a magicNumber property on an object named target:
var numberPtr = new PseudoPointer(target, "magicNumber");
myDynamicObjectA.numberPtr = numberPtr;
myDynamicObjectB.numberPtr = numberPtr;
myDynamicObjectC.numberPtr = numberPtr;
Now any object that has a reference to the pseudo-pointer can read and write the target property:
numberPtr.value = 42;
You could create a function and in which you give it the value and then subsequently assign it to all of those other variables.
Something like below:
var objectA:Number;
var objectB:Number;
...
function myFunction(newValue:Number):void
{
objectA = newValue;
objectB = newValue;
...
}
You could try setting a variable reference to a function. Then if you update that reference, it would return a different function.
var myFunc:Function;
function funcOne():int {
return 1;
}
function funcTwo():int {
return 2;
}
function getFunc():Function {
return myFunc;
}
myFunc = funcOne;
var myObjOne:Object = new Object();
myObjOne.objFunc = getFunc;
var myObjTwo:Object = new Object();
myObjTwo.objFunc = getFunc;
trace(myObjOne.objFunc.call().call()); // 1
trace(myObjTwo.objFunc.call().call()); // 1
myFunc = funcTwo;
trace(myObjOne.objFunc.call().call()); // 2
trace(myObjTwo.objFunc.call().call()); // 2
This allows any object to point at a single function and have what that returns be updateable for all of them simultaneously. It's not the prettiest code and it's not as type-safe as if ActionScript had delegates to enforce the signatures of what's set for myFunc, but it is still a pretty flexible model if you get creative with it.
Have those pointers point to something that contains the object or has the object as a property on it.
So
var myArr:Array = [new YourObject()];
var client1:ArrayClient = new ArrayClient();
client1.array = myArr;
var client2:ArrayClient = new ArrayClient();
client2.array = myArr;
myArr[0] = new YourObject();
//client1.array[0]==client2.array[0]==the new object
However, this seems to be a great way to get yourself into a lot of trouble really quickly. What's the use case?

Changes to AS3 array of string variables doesn't update the variables themselves

I have to assume I am missing something simple, or I am not understanding something, but I can't seem to figure this.
I have a list of strings that I add to an array, then attempt to set those values in a for-loop using data that I read in. The array gets updated, but the values the array contains do not. I also have an array of buttons that I update this same way that works great, but strings don't seem to work the same way. I have tried moving the string array to give it full scope, and still nothing... What am I missing?
public class test extends Sprite
{
// Declare a list of strings
protected var title0:String = undefined;
protected var title1:String = undefined;
protected var title2:String = undefined;
protected function onDataLoad(evt:Event):void{
var titleArray:Array = new Array(title0,title1,title2); // Array of strings
for(i=0; i<=evt.target.data.count; i++){
titleArray[i] = evt.target.data["title"+i]; // attempt to set the title values
}
}
protected function function0(e:Event):void {
trace("My title is: " + title0); // title0 is null
}
}
It has to do with how AS3 stores Array elements. You would need to explicitly set both the instance property (title1, title2, title3) and the associated array element (array[0], array[1], array[2]) to achieve what you want. Or, you could drop the instance properties (title0, title1, title2) completely and just use the array to store your values. Something like:
public class test extends Sprite
{
protected var _titleArray:Array = [];
protected function onDataLoad(evt:Event):void{
for(i=0; i<=evt.target.data.count; i++){
// set the title values
_titleArray[i] = evt.target.data["title"+i];
}
}
protected function function0(e:Event):void {
trace("My title is: " + _titleArray[0]);
}
}